#include <srs_app_config.hpp>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// file operations.
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <vector>
#include <algorithm>
using namespace std;

#include <srs_kernel_error.hpp>
#include <srs_kernel_log.hpp>
#include <srs_rtmp_utility.hpp>
#include <srs_core_autofree.hpp>
#include <srs_app_source.hpp>
#include <srs_kernel_file.hpp>
#include <srs_app_utility.hpp>
#include <srs_core_performance.hpp>
#include <srs_rtmp_amf0.hpp>

#define RAPIDJSON_HAS_STDSTRING 1
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>

#include <Config/ConfigInfo.hpp>
#include <etcd/et_report.h>
#include <etcd/et_keyv.h>
#include <etcd/et_tools.h>
#include <utils/tools.h>
#include <utils/tools_curl.h>

using namespace _srs_internal;

// the version to identify the core.
const char* _srs_version = "XCORE-" RTMP_SIG_SRS_SERVER;

// when user config an invalid value, macros to perfer true or false.
#define SRS_CONF_PERFER_FALSE(conf_arg) conf_arg == "on"
#define SRS_CONF_PERFER_TRUE(conf_arg) conf_arg != "off"

///////////////////////////////////////////////////////////
// default consts values
///////////////////////////////////////////////////////////
static string SRS_CONF_DEFAULT_PID_FILE("./log/srs.pid");
static string SRS_CONF_DEFAULT_LOG_FILE("./log/srs.log");
static string SRS_CONF_DEFAULT_LOG_LEVEL("trace");
static string SRS_CONF_DEFAULT_LOG_TANK_CONSOLE("console");
static string SRS_CONF_DEFAULT_COFNIG_FILE("conf/srs.conf");
static string SRS_CONF_DEFAULT_FF_LOG_DIR("./log");
static string SRS_CONF_DEFAULT_LOCAL_DIR("./");
#define SRS_CONF_DEFAULT_UTC_TIME false

#define SRS_CONF_DEFAULT_MAX_CONNECTIONS 1000
static string SRS_CONF_DEFAULT_HLS_PATH("./objs/hls");
static string SRS_CONF_DEFAULT_HLS_M3U8_FILE("[app]/[stream]/live.m3u8");
static string SRS_CONF_DEFAULT_HLS_TS_FILE("[app]/[stream]/[seq].ts");
#define SRS_CONF_DEFAULT_HLS_TS_FLOOR false
#define SRS_CONF_DEFAULT_HLS_FRAGMENT 10
#define SRS_CONF_DEFAULT_HLS_TD_RATIO 1.5
#define SRS_CONF_DEFAULT_HLS_AOF_RATIO 2.0
#define SRS_CONF_DEFAULT_HLS_WINDOW 60
static string SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE("ignore");
static string SRS_CONF_DEFAULT_HLS_ON_ERROR_DISCONNECT("disconnect");
static string SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE("continue");
#define SRS_CONF_DEFAULT_HLS_ON_ERROR SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE
static string SRS_CONF_DEFAULT_HLS_ACODEC("aac");
static string SRS_CONF_DEFAULT_HLS_VCODEC("h264");
#define SRS_CONF_DEFAULT_HLS_CLEANUP true
#define SRS_CONF_DEFAULT_HLS_WAIT_KEYFRAME true
#define SRS_CONF_DEFAULT_HLS_NB_NOTIFY 64
//static string SRS_CONF_DEFAULT_DVR_PATH("./objs/dvr/[app]/[stream]/[timestamp].flv");
static string SRS_CONF_DEFAULT_DVR_PATH("./objs/dvr/[app]/[stream]/[2006][01][02]_[15][04][05]_[timestamp].flv");
static string SRS_CONF_DEFAULT_DVR_PLAN_SESSION("session");
static string SRS_CONF_DEFAULT_DVR_PLAN_SEGMENT("segment");
static string SRS_CONF_DEFAULT_DVR_PLAN_APPEND("append");
#define SRS_CONF_DEFAULT_DVR_PLAN SRS_CONF_DEFAULT_DVR_PLAN_SESSION
#define SRS_CONF_DEFAULT_DVR_DURATION 30
static string SRS_CONF_DEFAULT_TIME_JITTER("full");
static string SRS_CONF_DEFAULT_EMPTY_STR("");
#define SRS_CONF_DEFAULT_ATC_AUTO true
#define SRS_CONF_DEFAULT_MIX_CORRECT false
// in seconds, the paused queue length.
#define SRS_CONF_DEFAULT_PAUSED_LENGTH 10
// the interval in seconds for bandwidth check
#define SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL 30
// the interval in seconds for bandwidth check
#define SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS 1000

static string SRS_CONF_DEFAULT_HTTP_MOUNT("[vhost]/");
static string SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT("[app]/[stream]/live.flv");
#define SRS_CONF_DEFAULT_HTTP_DIR SRS_CONF_DEFAULT_HLS_PATH
#define SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE 0

static string SRS_CONF_DEFAULT_HTTP_STREAM_PORT("8080");
static string SRS_CONF_DEFAULT_HTTP_API_PORT("1985");
#define SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN true

#define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED false
#define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL 9.9
static string SRS_CONF_DEFAULT_HTTP_HEAETBEAT_URL("http://" SRS_CONSTS_LOCALHOST ":8085/api/v1/servers");
#define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_SUMMARIES false

#define SRS_CONF_DEFAULT_SECURITY_ENABLED false

#define SRS_CONF_DEFAULT_STREAM_CASTER_ENABLED false
static string SRS_CONF_DEFAULT_STREAM_CASTER_MPEGTS_OVER_UDP("mpegts_over_udp");
static string SRS_CONF_DEFAULT_STREAM_CASTER_RTSP("rtsp");
static string SRS_CONF_DEFAULT_STREAM_CASTER_FLV("flv");

#define SRS_CONF_DEFAULT_STATS_NETWORK_DEVICE_INDEX 0

#define SRS_CONF_DEFAULT_PITHY_PRINT_MS 10000

static string SRS_CONF_DEFAULT_INGEST_TYPE_FILE("file");
static string SRS_CONF_DEFAULT_INGEST_TYPE_STREAM("stream");

static string SRS_CONF_DEFAULT_TRANSCODE_IFORMAT("flv");
static string SRS_CONF_DEFAULT_TRANSCODE_OFORMAT("flv");

#define SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE false
static string SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST("[vhost]");

// hds default value
static string SRS_CONF_DEFAULT_HDS_PATH("./objs/hds");
#define SRS_CONF_DEFAULT_HDS_WINDOW     (60)
#define SRS_CONF_DEFAULT_HDS_FRAGMENT   (10)

// '\n'
#define SRS_LF (char)SRS_CONSTS_LF

// '\r'
#define SRS_CR (char)SRS_CONSTS_CR

bool is_common_space(char ch) {
    return (ch == ' ' || ch == '\t' || ch == SRS_CR || ch == SRS_LF);
}

static string DEFAULT_STR = "";

SrsConfDirective::SrsConfDirective() {
}

SrsConfDirective::~SrsConfDirective() {
    for (auto it = directives.begin(); it != directives.end(); ++it) {
        auto directive = *it;
        srs_freep(directive);
    }
    directives.clear();
}

const string& SrsConfDirective::arg0() {
    if (args.size() > 0) {
        return args.at(0);
    }
    return DEFAULT_STR;
}

const string& SrsConfDirective::arg1() {
    if (args.size() > 1) {
        return args.at(1);
    }
    return DEFAULT_STR;
}

const string& SrsConfDirective::arg2() {
    if (args.size() > 2) {
        return args.at(2);
    }
    return DEFAULT_STR;
}

SrsConfDirective* SrsConfDirective::at(int index) {
    if (index < (int)directives.size()) {
        return directives.at(index);
    }
    return nullptr;
}

SrsConfDirective* SrsConfDirective::get(const string& _name) {
    for (auto it = directives.begin(); it != directives.end(); ++it) {
        auto directive = *it;
        if (directive != nullptr && directive->name == _name) {
            return directive;
        }
    }
    return nullptr;
}

const std::string& SrsConfDirective::getArg(const std::string& _name, int index) {
    auto cfg = get(_name);
    if (cfg != nullptr) {
        if (index == 0) {
            return cfg->arg0();
        } else if (index == 1) {
            return cfg->arg1();
        } else if (index == 2) {
            return cfg->arg2();
        }
    }
    return DEFAULT_STR;
}

SrsConfDirective* SrsConfDirective::get(const string& _name, const string& _arg0) {
    for (auto it = directives.begin(); it != directives.end(); ++it) {
        auto directive = *it;
        if (directive != nullptr && directive->name == _name && directive->arg0() == _arg0) {
            return directive;
        }
    }
    return nullptr;
}

bool SrsConfDirective::is_vhost() {
    return name == "vhost";
}

bool SrsConfDirective::is_stream_caster() {
    return name == "stream_caster";
}

int SrsConfDirective::parse(SrsConfigBuffer* buffer) {
    return parse_conf(buffer, parse_file);
}

// see: ngx_conf_parse
int SrsConfDirective::parse_conf(SrsConfigBuffer* buffer, SrsDirectiveType type) {
    int ret = ERROR_SUCCESS;

    while (true) {
        std::vector<string> args;
        int line_start = 0;
        ret = read_token(buffer, args, line_start);

        /**
        * ret maybe:
        * ERROR_SYSTEM_CONFIG_INVALID           error.
        * ERROR_SYSTEM_CONFIG_DIRECTIVE         directive terminated by ';' found
        * ERROR_SYSTEM_CONFIG_BLOCK_START       token terminated by '{' found
        * ERROR_SYSTEM_CONFIG_BLOCK_END         the '}' found
        * ERROR_SYSTEM_CONFIG_EOF               the config file is done
        */
        if (ret == ERROR_SYSTEM_CONFIG_INVALID) {
            return ret;
        }
        if (ret == ERROR_SYSTEM_CONFIG_BLOCK_END) {
            if (type != parse_block) {
                srs_error("line %d: unexpected \"}\", ret=%d", buffer->line, ret);
                return ret;
            }
            return ERROR_SUCCESS;
        }
        if (ret == ERROR_SYSTEM_CONFIG_EOF) {
            if (type == parse_block) {
                srs_error("line %d: unexpected end of file, expecting \"}\", ret=%d", conf_line, ret);
                return ret;
            }
            return ERROR_SUCCESS;
        }

        if (args.empty()) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("line %d: empty directive. ret=%d", conf_line, ret);
            return ret;
        }

        // build directive tree.
        SrsConfDirective* directive = new SrsConfDirective();

        directive->conf_line = line_start;
        directive->name = args[0];
        args.erase(args.begin());
        directive->args.swap(args);

        directives.push_back(directive);

        if (ret == ERROR_SYSTEM_CONFIG_BLOCK_START) {
            if ((ret = directive->parse_conf(buffer, parse_block)) != ERROR_SUCCESS) {
                return ret;
            }
        }
    }
    return ret;
}

void directiveAppend(ConfigInfo::bptree& tree, std::vector<SrsConfDirective*>& directives, SrsConfDirective* fdir) {
    size_t from = 0;
    bool hasChar = false;

    for (auto node : tree) {
        auto& first = node.first;
        auto& second = node.second;
        if (first.empty() == true) {
            if (fdir != nullptr) {
                fdir->args.push_back(second.data());
            }
            continue;
        }
        if (first == "_arg" && fdir != nullptr) {
            from = 0;
            hasChar = false;

            auto arg = second.data();
            for (size_t i = 0; i < arg.length(); i++) {
                if (arg[i] == ' ' || arg[i] == '\t') {
                    if (hasChar == true) {
                        fdir->args.push_back(arg.substr(from, i - from));
                    }
                    from = i + 1;
                    hasChar = false;
                } else {
                    hasChar = true;
                }
            }
            if (from < arg.length() && hasChar == true) {
                fdir->args.push_back(arg.substr(from));
            }
            continue;
        }
        SrsConfDirective* sdir = new SrsConfDirective();
        sdir->name = first;
        if (second.empty() == true) {
            sdir->args.push_back(second.data());
        } else {
            directiveAppend(second, sdir->directives, sdir);
        }
        directives.push_back(sdir);
    }
}

int SrsConfDirective::parse_etcd(ConfigInfo* cfg) {
    directiveAppend(cfg->getbptree(), directives, nullptr);
    return ERROR_SUCCESS;
}

// see: ngx_conf_read_token
int SrsConfDirective::read_token(SrsConfigBuffer* buffer, vector<string>& args, int& line_start) {
    int ret = ERROR_SUCCESS;

    char* pstart = buffer->pos;

    bool sharp_comment = false;

    bool d_quoted = false;
    bool s_quoted = false;

    bool need_space = false;
    bool last_space = true;

    while (true) {
        if (buffer->empty()) {
            ret = ERROR_SYSTEM_CONFIG_EOF;

            if (!args.empty() || !last_space) {
                srs_error("line %d: unexpected end of file, expecting ; or \"}\"", buffer->line);
                return ERROR_SYSTEM_CONFIG_INVALID;
            }
            srs_trace("config parse complete");

            return ret;
        }

        char ch = *buffer->pos++;

        if (ch == SRS_LF) {
            buffer->line++;
            sharp_comment = false;
        }

        if (sharp_comment) {
            continue;
        }

        if (need_space) {
            if (is_common_space(ch)) {
                last_space = true;
                need_space = false;
                continue;
            }
            if (ch == ';') {
                return ERROR_SYSTEM_CONFIG_DIRECTIVE;
            }
            if (ch == '{') {
                return ERROR_SYSTEM_CONFIG_BLOCK_START;
            }
            srs_error("line %d: unexpected '%c'", buffer->line, ch);
            return ERROR_SYSTEM_CONFIG_INVALID;
        }

        // last charecter is space.
        if (last_space) {
            if (is_common_space(ch)) {
                continue;
            }
            pstart = buffer->pos - 1;
            switch (ch) {
                case ';':
                    if (args.size() == 0) {
                        srs_error("line %d: unexpected ';'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_DIRECTIVE;
                case '{':
                    if (args.size() == 0) {
                        srs_error("line %d: unexpected '{'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_BLOCK_START;
                case '}':
                    if (args.size() != 0) {
                        srs_error("line %d: unexpected '}'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_BLOCK_END;
                case '#':
                    sharp_comment = 1;
                    continue;
                case '"':
                    pstart++;
                    d_quoted = true;
                    last_space = 0;
                    continue;
                case '\'':
                    pstart++;
                    s_quoted = true;
                    last_space = 0;
                    continue;
                default:
                    last_space = 0;
                    continue;
            }
        } else {
            // last charecter is not space
            if (line_start == 0) {
                line_start = buffer->line;
            }

            bool found = false;
            if (d_quoted) {
                if (ch == '"') {
                    d_quoted = false;
                    need_space = true;
                    found = true;
                }
            } else if (s_quoted) {
                if (ch == '\'') {
                    s_quoted = false;
                    need_space = true;
                    found = true;
                }
            } else if (is_common_space(ch) || ch == ';' || ch == '{') {
                last_space = true;
                found = 1;
            }

            if (found) {
                int len = (int)(buffer->pos - pstart);
                char* aword = new char[len];
                memcpy(aword, pstart, len);
                aword[len - 1] = 0;

                string word_str = aword;
                if (!word_str.empty()) {
                    args.push_back(word_str);
                }
                srs_freepa(aword);

                if (ch == ';') {
                    return ERROR_SYSTEM_CONFIG_DIRECTIVE;
                }
                if (ch == '{') {
                    return ERROR_SYSTEM_CONFIG_BLOCK_START;
                }
            }
        }
    }
    return ret;
}

SrsConfig::SrsConfig() {
    show_help = false;
    show_version = false;
    test_conf = false;
    use_http_report = false;

    root = new SrsConfDirective();
    root->conf_line = 0;
    root->name = "root";
}

SrsConfig::~SrsConfig() {
    srs_freep(root);
    if (etcdReporter) {
        etcdReporter = nullptr;
    }
}

bool SrsConfig::is_dolphin() {
    return (dolphin_rtmp_port.empty() == false || dolphin_http_port.empty() == false);
}

void SrsConfig::set_config_directive(SrsConfDirective* parent, const string& dir, const string& value) {
    auto d = parent->get(dir);
    if (d == nullptr) {
        d = new SrsConfDirective();
        if (dir.empty() == false) {
            d->name = dir;
        }
        parent->directives.push_back(d);
    }

    d->args.clear();
    if (value.empty() == false) {
        d->args.push_back(value);
    }
}

void SrsConfig::subscribe(ISrsReloadHandler* handler) {
    auto it = std::find(subscribes.begin(), subscribes.end(), handler);
    if (it != subscribes.end()) {
        return;
    }
    subscribes.push_back(handler);
}

void SrsConfig::unsubscribe(ISrsReloadHandler* handler) {
    auto it = std::find(subscribes.begin(), subscribes.end(), handler);
    if (it == subscribes.end()) {
        return;
    }
    subscribes.erase(it);
}

int SrsConfig::reload() {
    int ret = ERROR_SUCCESS;
    SrsConfig conf;

    if (config_file.empty() == false) {
        if ((ret = conf.parse_file(config_file.c_str())) != ERROR_SUCCESS) {
            srs_error("ignore config reloader parse file failed. ret=%d", ret);
            ret = ERROR_SUCCESS;
            return ret;
        }
        srs_info("config reloader parse file success.");
    }

    if (srs_etcd_addr.empty() == false) {
        if ((ret = conf.parse_etcd(srs_etcd_addr.c_str())) != ERROR_SUCCESS) {
            srs_error("ignore config reloader parse etcd failed. ret=%d", ret);
            ret = ERROR_SUCCESS;
            return ret;
        }
        srs_info("config reloader parse etcd success.");
    }

    if ((ret = conf.check_config()) != ERROR_SUCCESS) {
        srs_error("ignore config reloader check config failed. ret=%d", ret);
        ret = ERROR_SUCCESS;
        return ret;
    }

    return reload_conf(&conf);
}

int SrsConfig::reload_vhost(SrsConfDirective* old_root) {
    int ret = ERROR_SUCCESS;

    // state graph
    //      old_vhost       new_vhost
    //      DISABLED    =>  ENABLED
    //      ENABLED     =>  DISABLED
    //      ENABLED     =>  ENABLED (modified)

    // collect all vhost names
    std::vector<string> vhosts;
    for (int i = 0; i < (int)root->directives.size(); i++) {
        auto vhost = root->at(i);
        if (vhost == nullptr || vhost->name != "vhost") {
            continue;
        }
        vhosts.push_back(vhost->arg0());
    }
    for (int i = 0; i < (int)old_root->directives.size(); i++) {
        auto vhost = old_root->at(i);
        if (vhost == nullptr || vhost->name != "vhost") {
            continue;
        }
        if (root->get("vhost", vhost->arg0())) {
            continue;
        }
        vhosts.push_back(vhost->arg0());
    }

    // process each vhost
    for (int i = 0; i < (int)vhosts.size(); i++) {
        auto& vhost = vhosts.at(i);

        SrsConfDirective* old_vhost = old_root->get("vhost", vhost);
        SrsConfDirective* new_vhost = root->get("vhost", vhost);

        //      DISABLED    =>  ENABLED
        if (!get_vhost_enabled(old_vhost) && get_vhost_enabled(new_vhost)) {
            srs_trace("vhost %s added, reload it.", vhost.c_str());
            for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                auto subscribe = *it;
                if (subscribe == nullptr) {
                    continue;
                }
                if ((ret = subscribe->on_reload_vhost_added(vhost)) != ERROR_SUCCESS) {
                    srs_error("notify subscribes added vhost %s failed. ret=%d", vhost.c_str(), ret);
                    return ret;
                }
            }
            srs_trace("reload new vhost %s success.", vhost.c_str());
            continue;
        }

        //      ENABLED     =>  DISABLED
        if (get_vhost_enabled(old_vhost) && !get_vhost_enabled(new_vhost)) {
            srs_trace("vhost %s removed, reload it.", vhost.c_str());
            for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                auto subscribe = *it;
                if (subscribe == nullptr) {
                    continue;
                }
                if ((ret = subscribe->on_reload_vhost_removed(vhost)) != ERROR_SUCCESS) {
                    srs_error("notify subscribes removed vhost %s failed. ret=%d", vhost.c_str(), ret);
                    return ret;
                }
            }
            srs_trace("reload removed vhost %s success.", vhost.c_str());
            continue;
        }

        // mode, never supports reload.
        // first, for the origin and edge role change is too complex.
        // second, the vhosts in origin device group normally are all origin,
        //      they never change to edge sometimes.
        // third, the origin or upnode device can always be restart,
        //      edge will retry and the users connected to edge are ok.
        // it's ok to add or remove edge/origin vhost.
        if (get_vhost_is_edge(old_vhost) != get_vhost_is_edge(new_vhost)) {
            ret = ERROR_RTMP_EDGE_RELOAD;
            srs_error("reload never supports mode changed. ret=%d", ret);
            return ret;
        }

        // the auto reload configs:
        //      publish.parse_sps

        //      ENABLED     =>  ENABLED (modified)
        if (get_vhost_enabled(new_vhost) && get_vhost_enabled(old_vhost)) {
            srs_trace("vhost %s maybe modified, reload its detail.", vhost.c_str());
            // atc, only one per vhost
            if (!srs_directive_equals(new_vhost->get("atc"), old_vhost->get("atc"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_atc(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes atc failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload atc success.", vhost.c_str());
            }
            // gop_cache, only one per vhost
            if (!srs_directive_equals(new_vhost->get("gop_cache"), old_vhost->get("gop_cache"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_gop_cache(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes gop_cache failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload gop_cache success.", vhost.c_str());
            }
            // queue_length, only one per vhost
            if (!srs_directive_equals(new_vhost->get("queue_length"), old_vhost->get("queue_length"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_queue_length(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes queue_length failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload queue_length success.", vhost.c_str());
            }
            // time_jitter, only one per vhost
            if (!srs_directive_equals(new_vhost->get("time_jitter"), old_vhost->get("time_jitter"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_time_jitter(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes time_jitter failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload time_jitter success.", vhost.c_str());
            }
            // mix_correct, only one per vhost
            if (!srs_directive_equals(new_vhost->get("mix_correct"), old_vhost->get("mix_correct"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_mix_correct(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes mix_correct failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload mix_correct success.", vhost.c_str());
            }
            // forward, only one per vhost
            if (!srs_directive_equals(new_vhost->get("forward"), old_vhost->get("forward"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_forward(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes forward failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload forward success.", vhost.c_str());
            }
            // hls, only one per vhost
            // @remark, the hls_on_error directly support reload.
            if (!srs_directive_equals(new_vhost->get("hls"), old_vhost->get("hls"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_hls(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes hls failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload hls success.", vhost.c_str());
            }

            // hds reload
            if (!srs_directive_equals(new_vhost->get("hds"), old_vhost->get("hds"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_hds(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes hds failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload hds success.", vhost.c_str());
            }

            // dvr, only one per vhost
            if (!srs_directive_equals(new_vhost->get("dvr"), old_vhost->get("dvr"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_dvr(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes dvr failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload hlsdvrsuccess.", vhost.c_str());
            }
            // mr, only one per vhost
            if (!srs_directive_equals(new_vhost->get("mr"), old_vhost->get("mr"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_mr(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes mr failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload mr success.", vhost.c_str());
            }
            // chunk_size, only one per vhost.
            if (!srs_directive_equals(new_vhost->get("chunk_size"), old_vhost->get("chunk_size"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_chunk_size(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes chunk_size failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload chunk_size success.", vhost.c_str());
            }
            // mw, only one per vhost
            if (!srs_directive_equals(new_vhost->get("mw_latency"), old_vhost->get("mw_latency"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_mw(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes mw failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload mw success.", vhost.c_str());
            }
            // smi(send_min_interval), only one per vhost
            if (!srs_directive_equals(new_vhost->get("send_min_interval"), old_vhost->get("send_min_interval"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_smi(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes smi failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload smi success.", vhost.c_str());
            }
            // tcp_nodelay, only one per vhost
            if (!srs_directive_equals(new_vhost->get("tcp_nodelay"), old_vhost->get("tcp_nodelay"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_tcp_nodelay(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes tcp_nodelay failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload tcp_nodelay success.", vhost.c_str());
            }
            // publish_1stpkt_timeout, only one per vhost
            if (!srs_directive_equals(new_vhost->get("publish_1stpkt_timeout"), old_vhost->get("publish_1stpkt_timeout"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_p1stpt(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes p1stpt failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload p1stpt success.", vhost.c_str());
            }
            // publish_normal_timeout, only one per vhost
            if (!srs_directive_equals(new_vhost->get("publish_normal_timeout"), old_vhost->get("publish_normal_timeout"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_pnt(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes pnt failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload pnt success.", vhost.c_str());
            }
            // min_latency, only one per vhost
            if (!srs_directive_equals(new_vhost->get("min_latency"), old_vhost->get("min_latency"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_realtime(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes min_latency failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload min_latency success.", vhost.c_str());
            }
            // http, only one per vhost.
            if (!srs_directive_equals(new_vhost->get("http"), old_vhost->get("http"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_http_updated()) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes http failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload http success.", vhost.c_str());
            }
            // http_static, only one per vhost.
            // @remark, http_static introduced as alias of http.
            if (!srs_directive_equals(new_vhost->get("http_static"), old_vhost->get("http_static"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_http_updated()) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes http_static failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload http_static success.", vhost.c_str());
            }
            // http_remux, only one per vhost.
            if (!srs_directive_equals(new_vhost->get("http_remux"), old_vhost->get("http_remux"))) {
                for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                    auto subscribe = *it;
                    if (subscribe == nullptr) {
                        continue;
                    }
                    if ((ret = subscribe->on_reload_vhost_http_remux_updated(vhost)) != ERROR_SUCCESS) {
                        srs_error("vhost %s notify subscribes http_remux failed. ret=%d", vhost.c_str(), ret);
                        return ret;
                    }
                }
                srs_trace("vhost %s reload http_remux success.", vhost.c_str());
            }
            // transcode, many per vhost.
            if ((ret = reload_transcode(new_vhost, old_vhost)) != ERROR_SUCCESS) {
                return ret;
            }
            // ingest, many per vhost.
            if ((ret = reload_ingest(new_vhost, old_vhost)) != ERROR_SUCCESS) {
                return ret;
            }
            continue;
        }
        srs_trace("ignore reload vhost, enabled old: %d, new: %d", get_vhost_enabled(old_vhost), get_vhost_enabled(new_vhost));
    }
    return ret;
}

int SrsConfig::reload_conf(SrsConfig* conf) {
    int ret = ERROR_SUCCESS;

    SrsConfDirective* old_root = root;
    SrsAutoFree(SrsConfDirective, old_root);

    root = conf->root;
    conf->root = nullptr;

    // never support reload:
    //      daemon
    //
    // always support reload without additional code:
    //      chunk_size, ff_log_dir,
    //      bandcheck, http_hooks, heartbeat,
    //      token_traverse, debug_srs_upnode,
    //      security

    // merge config: max_connections
    if (!srs_directive_equals(root->get("max_connections"), old_root->get("max_connections"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_max_conns()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload max_connections failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload max_connections success.");
    }

    // merge config: listen
    if (!srs_directive_equals(root->get("listen"), old_root->get("listen"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_listen()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload listen failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload listen success.");
    }

    // merge config: pid
    if (!srs_directive_equals(root->get("pid"), old_root->get("pid"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_pid()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload pid failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload pid success.");
    }

    // merge config: srs_log_tank
    if (!srs_directive_equals(root->get("srs_log_tank"), old_root->get("srs_log_tank"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_log_tank()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload srs_log_tank failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload srs_log_tank success.");
    }

    // merge config: srs_log_level
    if (!srs_directive_equals(root->get("srs_log_level"), old_root->get("srs_log_level"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_log_level()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload srs_log_level failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload srs_log_level success.");
    }

    // merge config: srs_log_file
    if (!srs_directive_equals(root->get("srs_log_file"), old_root->get("srs_log_file"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_log_file()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload srs_log_file failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload srs_log_file success.");
    }

    // merge config: utc_time
    if (!srs_directive_equals(root->get("utc_time"), old_root->get("utc_time"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_utc_time()) != ERROR_SUCCESS) {
                srs_error("notify subscribes reload utc_time failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload utc_time success.");
    }

    // merge config: pithy_print_ms
    if (!srs_directive_equals(root->get("pithy_print_ms"), old_root->get("pithy_print_ms"))) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_pithy_print()) != ERROR_SUCCESS) {
                srs_error("notify subscribes pithy_print_ms failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload pithy_print_ms success.");
    }

    // merge config: http_api
    if ((ret = reload_http_api(old_root)) != ERROR_SUCCESS) {
        return ret;
    }

    // merge config: http_stream
    if ((ret = reload_http_stream(old_root)) != ERROR_SUCCESS) {
        return ret;
    }

    // TODO: FIXME: support reload stream_caster.
    // merge config: vhost
    if ((ret = reload_vhost(old_root)) != ERROR_SUCCESS) {
        return ret;
    }
    return ret;
}

int SrsConfig::reload_http_api(SrsConfDirective* old_root) {
    int ret = ERROR_SUCCESS;

    // state graph
    //      old_http_api    new_http_api
    //      DISABLED    =>  ENABLED
    //      ENABLED     =>  DISABLED
    //      ENABLED     =>  ENABLED (modified)

    SrsConfDirective* new_http_api = root->get("http_api");
    SrsConfDirective* old_http_api = old_root->get("http_api");

    // DISABLED    =>      ENABLED
    if (!get_http_api_enabled(old_http_api) && get_http_api_enabled(new_http_api)) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_api_enabled()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_api disabled=>enabled failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload disabled=>enabled http_api success.");
        return ret;
    }

    // ENABLED     =>      DISABLED
    if (get_http_api_enabled(old_http_api) && !get_http_api_enabled(new_http_api)) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_api_disabled()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_api enabled=>disabled failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload enabled=>disabled http_api success.");
        return ret;
    }

    //      ENABLED     =>  ENABLED (modified)
    if (get_http_api_enabled(old_http_api) && get_http_api_enabled(new_http_api)
            && !srs_directive_equals(old_http_api, new_http_api)
       ) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_api_enabled()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_api enabled modified failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload enabled modified http_api success.");
        return ret;
    }

    srs_trace("reload http_api not changed success.");

    return ret;
}

int SrsConfig::reload_http_stream(SrsConfDirective* old_root) {
    int ret = ERROR_SUCCESS;

    // state graph
    //      old_http_stream     new_http_stream
    //      DISABLED    =>      ENABLED
    //      ENABLED     =>      DISABLED
    //      ENABLED     =>      ENABLED (modified)

    SrsConfDirective* new_http_stream = root->get("http_stream");
    // http_stream rename to http_server in SRS2.
    if (!new_http_stream) {
        new_http_stream = root->get("http_server");
    }

    SrsConfDirective* old_http_stream = old_root->get("http_stream");
    // http_stream rename to http_server in SRS2.
    if (!old_http_stream) {
        old_http_stream = root->get("http_server");
    }

    // DISABLED    =>      ENABLED
    if (!get_http_stream_enabled(old_http_stream) && get_http_stream_enabled(new_http_stream)) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_stream_enabled()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_stream disabled=>enabled failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload disabled=>enabled http_stream success.");
        return ret;
    }

    // ENABLED     =>      DISABLED
    if (get_http_stream_enabled(old_http_stream) && !get_http_stream_enabled(new_http_stream)) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_stream_disabled()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_stream enabled=>disabled failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload enabled=>disabled http_stream success.");
        return ret;
    }

    //      ENABLED     =>  ENABLED (modified)
    if (get_http_stream_enabled(old_http_stream) && get_http_stream_enabled(new_http_stream)
            && !srs_directive_equals(old_http_stream, new_http_stream)
       ) {
        for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
            auto subscribe = *it;
            if (subscribe == nullptr) {
                continue;
            }
            if ((ret = subscribe->on_reload_http_stream_updated()) != ERROR_SUCCESS) {
                srs_error("notify subscribes http_stream enabled modified failed. ret=%d", ret);
                return ret;
            }
        }
        srs_trace("reload enabled modified http_stream success.");
        return ret;
    }

    srs_trace("reload http_stream not changed success.");
    return ret;
}

int SrsConfig::reload_transcode(SrsConfDirective* new_vhost, SrsConfDirective* old_vhost) {
    int ret = ERROR_SUCCESS;

    std::vector<SrsConfDirective*> old_transcoders;
    for (int i = 0; i < (int)old_vhost->directives.size(); i++) {
        auto conf = old_vhost->at(i);
        if (conf != nullptr && conf->name == "transcode") {
            old_transcoders.push_back(conf);
        }
    }

    std::vector<SrsConfDirective*> new_transcoders;
    for (int i = 0; i < (int)new_vhost->directives.size(); i++) {
        auto conf = new_vhost->at(i);
        if (conf != nullptr && conf->name == "transcode") {
            new_transcoders.push_back(conf);
        }
    }

    std::vector<ISrsReloadHandler*>::iterator it;

    auto& vhost = new_vhost->arg0();

    // to be simple:
    // whatever, once tiny changed of transcode,
    // restart all ffmpeg of vhost.
    bool changed = false;

    // discovery the removed ffmpeg.
    for (int i = 0; !changed && i < (int)old_transcoders.size(); i++) {
        auto old_transcoder = old_transcoders.at(i);
        if (old_transcoder == nullptr) {
            continue;
        }
        // if transcoder exists in new vhost, not removed, ignore.
        if (new_vhost->get("transcode", old_transcoder->arg0())) {
            continue;
        }
        changed = true;
    }

    // discovery the added ffmpeg.
    for (int i = 0; !changed && i < (int)new_transcoders.size(); i++) {
        auto new_transcoder = new_transcoders.at(i);
        if (new_transcoder == nullptr) {
            continue;
        }
        // if transcoder exists in old vhost, not added, ignore.
        if (old_vhost->get("transcode", new_transcoder->arg0())) {
            continue;
        }
        changed = true;
    }

    // for updated transcoders, restart them.
    for (int i = 0; !changed && i < (int)new_transcoders.size(); i++) {
        auto new_transcoder = new_transcoders.at(i);
        if (new_transcoder == nullptr) {
            continue;
        }
        auto old_transcoder = old_vhost->get("transcode", new_transcoder->arg0());
        if (old_transcoder == nullptr) {
            continue;
        }
        if (srs_directive_equals(new_transcoder, old_transcoder)) {
            continue;
        }
        changed = true;
    }

    // transcode, many per vhost
    if (changed) {
        for (it = subscribes.begin(); it != subscribes.end(); ++it) {
            ISrsReloadHandler* subscribe = *it;
            if ((ret = subscribe->on_reload_vhost_transcode(vhost)) != ERROR_SUCCESS) {
                srs_error("vhost %s notify subscribes transcode failed. ret=%d", vhost.c_str(), ret);
                return ret;
            }
        }
        srs_trace("vhost %s reload transcode success.", vhost.c_str());
    }

    return ret;
}

int SrsConfig::reload_ingest(SrsConfDirective* new_vhost, SrsConfDirective* old_vhost) {
    int ret = ERROR_SUCCESS;

    std::vector<SrsConfDirective*> old_ingesters;
    for (int i = 0; i < (int)old_vhost->directives.size(); i++) {
        auto conf = old_vhost->at(i);
        if (conf != nullptr && conf->name == "ingest") {
            old_ingesters.push_back(conf);
        }
    }

    std::vector<SrsConfDirective*> new_ingesters;
    for (int i = 0; i < (int)new_vhost->directives.size(); i++) {
        auto conf = new_vhost->at(i);
        if (conf != nullptr && conf->name == "ingest") {
            new_ingesters.push_back(conf);
        }
    }

    auto& vhost = new_vhost->arg0();

    // for removed ingesters, stop them.
    for (int i = 0; i < (int)old_ingesters.size(); i++) {
        auto old_ingester = old_ingesters.at(i);
        if (old_ingester == nullptr) {
            continue;
        }
        auto& ingest_id = old_ingester->arg0();
        auto new_ingester = new_vhost->get("ingest", ingest_id);
        // ENABLED => DISABLED
        if (get_ingest_enabled(old_ingester) && !get_ingest_enabled(new_ingester)) {
            // notice handler ingester removed.
            for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                auto subscribe = *it;
                if (subscribe == nullptr) {
                    continue;
                }
                if ((ret = subscribe->on_reload_ingest_removed(vhost, ingest_id)) != ERROR_SUCCESS) {
                    srs_error("vhost %s notify subscribes ingest=%s removed failed. ret=%d", vhost.c_str(), ingest_id.c_str(), ret);
                    return ret;
                }
            }
            srs_trace("vhost %s reload ingest=%s removed success.", vhost.c_str(), ingest_id.c_str());
        }
    }

    // for added ingesters, start them.
    for (int i = 0; i < (int)new_ingesters.size(); i++) {
        auto new_ingester = new_ingesters.at(i);
        if (new_ingester == nullptr) {
            continue;
        }
        auto& ingest_id = new_ingester->arg0();
        auto old_ingester = old_vhost->get("ingest", ingest_id);
        // DISABLED => ENABLED
        if (!get_ingest_enabled(old_ingester) && get_ingest_enabled(new_ingester)) {
            for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                auto subscribe = *it;
                if (subscribe == nullptr) {
                    continue;
                }
                if ((ret = subscribe->on_reload_ingest_added(vhost, ingest_id)) != ERROR_SUCCESS) {
                    srs_error("vhost %s notify subscribes ingest=%s added failed. ret=%d", vhost.c_str(), ingest_id.c_str(), ret);
                    return ret;
                }
            }
            srs_trace("vhost %s reload ingest=%s added success.", vhost.c_str(), ingest_id.c_str());
        }
    }

    // for updated ingesters, restart them.
    for (int i = 0; i < (int)new_ingesters.size(); i++) {
        auto new_ingester = new_ingesters.at(i);
        if (new_ingester == nullptr) {
            continue;
        }
        auto& ingest_id = new_ingester->arg0();
        auto old_ingester = old_vhost->get("ingest", ingest_id);
        // ENABLED => ENABLED
        if (get_ingest_enabled(old_ingester) && get_ingest_enabled(new_ingester)) {
            if (srs_directive_equals(new_ingester, old_ingester)) {
                continue;
            }
            // notice handler ingester removed.
            for (auto it = subscribes.begin(); it != subscribes.end(); ++it) {
                auto subscribe = *it;
                if (subscribe == nullptr) {
                    continue;
                }
                if ((ret = subscribe->on_reload_ingest_updated(vhost, ingest_id)) != ERROR_SUCCESS) {
                    srs_error("vhost %s notify subscribes ingest=%s updated failed. ret=%d", vhost.c_str(), ingest_id.c_str(), ret);
                    return ret;
                }
            }
            srs_trace("vhost %s reload ingest=%s updated success.", vhost.c_str(), ingest_id.c_str());
        }
    }

    srs_trace("ingest not changed for vhost=%s", vhost.c_str());
    return ret;
}

// see: ngx_get_options
int SrsConfig::parse_options(int argc, char** argv) {
    int ret = ERROR_SUCCESS;

    // argv
    for (int i = 0; i < argc; i++) {
        _argv.append(argv[i]);
        if (i < argc - 1) {
            _argv.append(" ");
        }
    }

    // config
    show_help = test_conf = false;
    for (int i = 1; i < argc; i++) {
        if ((ret = parse_argv(i, argv)) != ERROR_SUCCESS) {
            return ret;
        }
    }

    if (show_help) {
        print_help(argv);
        return ret;
    }

    if (show_version) {
        fprintf(stderr, "%s\n", RTMP_SIG_SRS_VERSION);
        return ret;
    }

    // first hello message.
    srs_trace(_srs_version);

    if (config_file.empty() == true && srs_etcd_addr.empty() == true) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("config file not specified, see help: %s -h, ret=%d", argv[0], ret);
        return ret;
    }

    if (config_file.empty() == false) {
        ret = parse_file(config_file.c_str());
    }

    if (srs_etcd_addr.empty() == false) {
        ret = parse_etcd(srs_etcd_addr.c_str());
    }

    if (ret == ERROR_SUCCESS) {
        ret = get_manage_etcd();
        if (ret != ERROR_SUCCESS) {
            srs_error("get etcd manage error, ret=%d", ret);
            return ret;
        }

        ret = report_etcd();
        if (ret != ERROR_SUCCESS) {
            srs_error("report etcd error, ret=%d", ret);
            return ret;
        }
    }

    if (test_conf) {
        // the parse_file never check the config,
        // we check it when user requires check config file.
        if (ret == ERROR_SUCCESS) {
            ret = check_config();
        }

        if (ret == ERROR_SUCCESS) {
            srs_trace("config file is ok");
            return ret;
        } else {
            srs_error("config file is invalid");
            return ret;
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check log name and level
    ////////////////////////////////////////////////////////////////////////
    if (true) {
        auto& log_filename = this->get_log_file();
        if (get_log_tank_file() && log_filename.empty()) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("must specifies the file to write log to. ret=%d", ret);
            return ret;
        }
        if (get_log_tank_file()) {
            srs_trace("write log to file %s", log_filename.c_str());
            srs_trace("you can: tailf %s", log_filename.c_str());
        } else {
            srs_trace("write log to console");
        }
    }
    return ret;
}

int SrsConfig::initialize_cwd() {
    int ret = ERROR_SUCCESS;
    // cwd
    char cwd[256];
    getcwd(cwd, sizeof(cwd));
    _cwd = cwd;
    return ret;
}

const string& SrsConfig::config() {
    return config_file;
}

int SrsConfig::parse_argv(int& i, char** argv) {
    int ret = ERROR_SUCCESS;
    char* p = argv[i];

    if (*p++ != '-') {
        show_help = true;
        return ret;
    }

    while (*p) {
        switch (*p++) {
            case '?':
            case 'h': {
                show_help = true;
            } break;
            case 't': {
                test_conf = true;
            } break;
            case 'p': {
                if (*p) {
                    dolphin_rtmp_port = p;
                    continue;
                }
                if (argv[++i]) {
                    dolphin_rtmp_port = argv[i];
                    continue;
                }
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("option \"-p\" requires params, ret=%d", ret);
                return ret;
            } break;
            case 'x': {
                if (*p) {
                    dolphin_http_port = p;
                    continue;
                }
                if (argv[++i]) {
                    dolphin_http_port = argv[i];
                    continue;
                }
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("option \"-x\" requires params, ret=%d", ret);
                return ret;
            } break;
            case 'v':
            case 'V': {
                show_version = true;
            } break;
            case 'c': {
                if (*p) {
                    config_file = p;
                    continue;
                }
                if (argv[++i]) {
                    config_file = argv[i];
                    continue;
                }
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("option \"-c\" requires parameter, ret=%d", ret);
                return ret;
            } break;
            case 'i': {
                if (*p) {
                    srs_server_id = p;
                    continue;
                }
                if (argv[++i]) {
                    srs_server_id = argv[i];
                    continue;
                }
            } break;
            case 'e': {
                if (*p) {
                    srs_etcd_addr = p;
                    continue;
                }
                if (argv[++i]) {
                    srs_etcd_addr = argv[i];
                    continue;
                }
            } break;
            default: {
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("invalid option: \"%c\", see help: %s -h, ret=%d", *(p - 1), argv[0], ret);
                return ret;
            } break;
        }
    }

    return ret;
}

void SrsConfig::print_help(char** argv) {
    if (argv == nullptr || argv[0] == nullptr) {
        return;
    }
    auto parg = argv[0];
    printf(
        RTMP_SIG_SRS_SERVER " " RTMP_SIG_SRS_COPYRIGHT "\n"
        "License: " RTMP_SIG_SRS_LICENSE "\n"
        "Primary: " RTMP_SIG_SRS_PRIMARY "\n"
        "Authors: " RTMP_SIG_SRS_AUTHROS "\n"
        "Build: " SRS_AUTO_BUILD_DATE " Configuration:" SRS_AUTO_USER_CONFIGURE "\n"
        "Features:" SRS_AUTO_CONFIGURE "\n""\n"
        "Usage: %s [-h?vV] [[-t] -c <filename>]\n"
        "\n"
        "Options:\n"
        "   -?, -h              : show this help and exit(0)\n"
        "   -v, -V              : show version and exit(0)\n"
        "   -t                  : test configuration file, exit(error_code).\n"
        "   -i server id        : server id for SRS\n"
        "   -c filename         : use configuration file for SRS\n"
        "   -e etcd path        : use configuration etcd for SRS\n"
        "For srs-dolphin:\n"
        "   -p  rtmp-port       : the rtmp port to listen.\n"
        "   -x  http-port       : the http port to listen.\n"
        "\n"
        RTMP_SIG_SRS_WEB "\n"
        RTMP_SIG_SRS_URL "\n"
        "Email: " RTMP_SIG_SRS_EMAIL "\n"
        "\n"
        "For example:\n"
        "   %s -v\n"
        "   %s -t -c %s \n"
        "   %s -c %s \n"
        "   %s -i 1 -e etcd://192.168.18.219:2379/srs/cfg \n",
        parg, parg,
        parg, SRS_CONF_DEFAULT_COFNIG_FILE.c_str(),
        parg, SRS_CONF_DEFAULT_COFNIG_FILE.c_str(),
        parg);
}

int SrsConfig::parse_file(const char* filename) {
    if (filename == nullptr) {
        filename = config_file.c_str();
    }

    if (filename == nullptr || strlen(filename) <= 0) {
        return ERROR_SYSTEM_CONFIG_INVALID;
    }

    SrsConfigBuffer buffer;
    auto ret = buffer.fullfill(filename);
    if (ret != ERROR_SUCCESS) {
        return ret;
    }

    ret = root->parse(&buffer);
    if (ret != ERROR_SUCCESS) {
        return ret;
    }

    append_dolphin();
    return ERROR_SUCCESS;
}

int SrsConfig::parse_etcd(const char* path) {
    if (path == nullptr) {
        path = srs_etcd_addr.c_str();
    }

    if (path == nullptr || strlen(path) <= 0) {
        return ERROR_SYSTEM_CONFIG_INVALID;
    }

    if (srs_server_id.empty() == false) {
        ConfigInfo::setEnv(ConfigInfo::E_EnvDef_ServerObjID, srs_server_id);
    }

    regEtcdConfig();

    ConfigInfo cfg;
    cfg.setFormat("json");
    auto rel = cfg.loadUrl(path);
    if (rel > 0) {
        srs_error("load url: %s error, ret=%d", path, rel);
        return ERROR_SYSTEM_CONFIG_DIRECTIVE;
    }

    auto ret = root->parse_etcd(&cfg);
    if (ret != ERROR_SUCCESS) {
        return ret;
    }

    append_dolphin();
    return ERROR_SUCCESS;
}

string SrsConfig::gain_etcd_report(int sid, int tid) {
    rapidjson::StringBuffer strBuf;
    rapidjson::Writer<rapidjson::StringBuffer> writer(strBuf);

    {
        writer.StartObject();

        // reqid
        writer.Key("reqid");
        writer.Int(sid);

        // id
        writer.Key("id");
        writer.Int(tid);

        // type
        writer.Key("type");
        writer.String(get_etcd_srv_type());

        // addr
        writer.Key("addr");
        writer.String(get_etcd_srv_addr());

        // httppt
        auto httppt = get_http_api_listen();
        if (httppt.empty() == false) {
            writer.Key("httppt");
            writer.Int(std::stoi(httppt));
        }

        // streampt
        auto streampt = get_http_stream_listen();
        if (streampt.empty() == false) {
            writer.Key("streampt");
            writer.Int(std::stoi(streampt));
        }

        // rtmppt
        string rtmppt;
        auto rtmppts = get_listens();
        if (rtmppts.empty() == false) {
            for (auto& pt : rtmppts) {
                rtmppt = rtmppt + pt + ",";
            }
        }
        if (rtmppt.empty() == false && rtmppt[rtmppt.length() - 1] == ',') {
            rtmppt = rtmppt.substr(0, rtmppt.length() - 1);
        }
        writer.Key("rtmppt");
        writer.String(rtmppt);

        writer.EndObject();
    }

    return strBuf.GetString();
}

int SrsConfig::report_etcd() {
    auto rptaddr = get_etcd_report_addr();
    if (rptaddr.empty() == true) {
        return ERROR_SUCCESS;
    }
   
    int sid = 0;
    if (srs_server_id.empty() == false) {
        sid = std::stoi(srs_server_id);
    }

    if (use_http_report == false) {
        etcdReporter.reset(new EtcdReporter(rptaddr));

        auto tid = etcdReporter->Report(get_etcd_srv_key(), gain_etcd_report(sid, 0), get_etcd_srv_ttl(), sid);
        if (tid <= 0) {
            return ERROR_ETCD_REPORT_SID;
        }

        etcdReporter->updateVal(gain_etcd_report(sid, tid));
        etcdReporter->setLogOk(get_etcd_report_log());

        srs_server_id = std::to_string(tid);
    } else if (etcd_callback_route.empty() == false) {
        auto data = gain_etcd_report(sid, 0);
        auto res = libcurlpost(etcd_callback_route + "report", "", data);
        if (res.empty() == true) {
            srs_warn("http post report uri failed, url=%s, request=%s", etcd_callback_route.c_str(), data.c_str());
            return ERROR_ETCD_REPORT_SID;
        }

        rapidjson::Document dom;
        if (dom.Parse(res.c_str()).HasParseError()) {
            return ERROR_JSON_PARSE;
        }

        auto end = dom.MemberEnd();
        auto rcode = dom.FindMember("code");
        if (rcode != end && rcode->value.GetInt() == 0) {
            auto itid = dom.FindMember("data");
            if (itid != end) {
                srs_server_id = std::to_string(itid->value.GetInt());
            }
        }
    }

    if (srs_server_id.empty() == false && srs_server_id != "0") {
        return ERROR_SUCCESS;
    }
    return ERROR_SERVER_ID_NIL;
}

int SrsConfig::get_manage_etcd() {
    auto rptaddr = get_etcd_report_addr();
    if (rptaddr.empty() == true) {
        return ERROR_SUCCESS;
    }

    auto smgr = get_etcd_srv_mgr();
    if (smgr.empty() == true) {
        return ERROR_SUCCESS;
    }

    etcdv3::ActionParameters param;
    param.key = smgr;

    EtcdKeyVal ekv(rptaddr);
    auto resp = ekv.getVal(param);

    if (resp.is_ok() == false) {
        return ERROR_ETCD_NOT_OK;
    }

    rapidjson::Document dom;
    if (dom.Parse(resp.value().as_string().c_str()).HasParseError()) {
        return ERROR_JSON_PARSE;
    }

    auto end = dom.MemberEnd();
    auto itip = dom.FindMember("ip"), itpt = dom.FindMember("port"), itrt = dom.FindMember("route");
    if (itip != end && itpt != end && itrt != end) {
        auto sip = std::string(itip->value.GetString(), itip->value.GetStringLength());
        auto sport = std::to_string(itpt->value.GetInt());
        auto route = std::string(itrt->value.GetString(), itrt->value.GetStringLength());
        etcd_callback_route = "http://" + sip + ":" + sport + route;

        auto pos = etcd_callback_route.length();
        if (pos > 0 && etcd_callback_route[pos - 1] != '/') {
            etcd_callback_route += '/';
        }
    }

    auto itUseHttp = dom.FindMember("use_http");
    if (itUseHttp != end) {
        use_http_report = itUseHttp->value.GetBool();
    }
    return ERROR_SUCCESS;
}

int SrsConfig::check_config() {
    int ret = ERROR_SUCCESS;

    srs_trace("srs checking config...");

    ////////////////////////////////////////////////////////////////////////
    // check empty
    ////////////////////////////////////////////////////////////////////////
    if (root->directives.size() == 0) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("conf is empty, ret=%d", ret);
        return ret;
    }

    ////////////////////////////////////////////////////////////////////////
    // check root directives.
    ////////////////////////////////////////////////////////////////////////
    for (int i = 0; i < (int)root->directives.size(); i++) {
        auto conf = root->at(i);
        if (conf == nullptr) {
            continue;
        }
        auto& n = conf->name;
        if (n != "listen" && n != "pid" && n != "chunk_size" && n != "ff_log_dir"
                && n != "srs_log_tank" && n != "srs_log_level" && n != "srs_log_file"
                && n != "max_connections" && n != "daemon" && n != "heartbeat"
                && n != "http_api" && n != "stats" && n != "vhost" && n != "pithy_print_ms"
                && n != "http_stream" && n != "http_server" && n != "stream_caster"
                && n != "utc_time" && n != "work_dir" && n != "asprocess" && n != "etcd"
           ) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("unsupported directive %s, ret=%d", n.c_str(), ret);
            return ret;
        }
    }
    if (true) {
        auto conf = get_http_api();
        if (conf != nullptr) {
            for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
                auto pcfg = conf->at(i);
                if (pcfg == nullptr) {
                    continue;
                }
                auto& n = pcfg->name;
                if (n != "enabled" && n != "listen" && n != "crossdomain") {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("unsupported http_api directive %s, ret=%d", n.c_str(), ret);
                    return ret;
                }
            }
        }
    }
    if (true) {
        auto conf = get_http_stream();
        if (conf != nullptr) {
            for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
                auto pcfg = conf->at(i);
                if (pcfg == nullptr) {
                    continue;
                }
                auto& n = pcfg->name;
                if (n != "enabled" && n != "listen" && n != "dir") {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("unsupported http_stream directive %s, ret=%d", n.c_str(), ret);
                    return ret;
                }
            }
        }
    }
    if (true) {
        auto conf = get_heartbeart();
        if (conf != nullptr) {
            for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
                auto pcfg = conf->at(i);
                if (pcfg == nullptr) {
                    continue;
                }
                auto& n = pcfg->name;
                if (n != "enabled" && n != "interval" && n != "url"
                        && n != "device_id" && n != "summaries"
                   ) {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("unsupported heartbeat directive %s, ret=%d", n.c_str(), ret);
                    return ret;
                }
            }
        }
    }
    if (true) {
        auto conf = get_stats();
        if (conf != nullptr) {
            for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
                auto pcfg = conf->at(i);
                if (pcfg == nullptr) {
                    continue;
                }
                auto& n = pcfg->name;
                if (n != "network" && n != "disk") {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("unsupported stats directive %s, ret=%d", n.c_str(), ret);
                    return ret;
                }
            }
        }
    }


    ////////////////////////////////////////////////////////////////////////
    // check listen for rtmp.
    ////////////////////////////////////////////////////////////////////////
    if (true) {
        vector<string> listens = get_listens();
        if (listens.size() <= 0) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("directive \"listen\" is empty, ret=%d", ret);
            return ret;
        }
        for (int i = 0; i < (int)listens.size(); i++) {
            auto& port = listens[i];
            if (port.empty() || ::atoi(port.c_str()) <= 0) {
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("directive listen invalid, port=%s, ret=%d", port.c_str(), ret);
                return ret;
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check max connections
    ////////////////////////////////////////////////////////////////////////
    if (get_max_connections() <= 0) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive max_connections invalid, max_connections=%d, ret=%d", get_max_connections(), ret);
        return ret;
    }

    // check max connections of system limits
    if (true) {
        int nb_consumed_fds = (int)get_listens().size();
        if (!get_http_api_listen().empty()) {
            nb_consumed_fds++;
        }
        if (!get_http_stream_listen().empty()) {
            nb_consumed_fds++;
        }
        if (get_log_tank_file()) {
            nb_consumed_fds++;
        }
        // 0, 1, 2 for stdin, stdout and stderr.
        nb_consumed_fds += 3;

        int nb_connections = get_max_connections();
        int nb_total = nb_connections + nb_consumed_fds;

#ifdef WIN32
        int max_open_files = 1024;
#else
        int max_open_files = (int)sysconf(_SC_OPEN_MAX);
#endif
        int nb_canbe = max_open_files - nb_consumed_fds - 1;

        // for each play connections, we open a pipe(2fds) to convert SrsConsumver to io,
        if (nb_total >= max_open_files) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("invalid max_connections=%d, required=%d, system limit to %d, "
                      "total=%d(max_connections=%d, nb_consumed_fds=%d), ret=%d. "
                      "you can change max_connections from %d to %d, or "
                      "you can login as root and set the limit: ulimit -HSn %d",
                      nb_connections, nb_total + 1, max_open_files,
                      nb_total, nb_connections, nb_consumed_fds,
                      ret, nb_connections, nb_canbe, nb_total + 1);
            return ret;
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check heartbeat
    ////////////////////////////////////////////////////////////////////////
    if (get_heartbeat_interval() <= 0) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive heartbeat interval invalid, interval=%" PRId64 ", ret=%d",
                  get_heartbeat_interval(), ret);
        return ret;
    }

    ////////////////////////////////////////////////////////////////////////
    // check stats
    ////////////////////////////////////////////////////////////////////////
    if (get_stats_network() < 0) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive stats network invalid, network=%d, ret=%d",
                  get_stats_network(), ret);
        return ret;
    }
    if (true) {
        auto& ips = srs_get_local_ipv4_ips();
        int index = get_stats_network();
        if (index >= (int)ips.size()) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("stats network invalid, total local ip count=%d, index=%d, ret=%d", (int)ips.size(), index, ret);
            return ret;
        }
        srs_warn("stats network use index=%d, ip=%s", index, ips.at(index).c_str());
    }
    if (true) {
        auto conf = get_stats_disk_device();
        if (conf == nullptr || (int)conf->args.size() <= 0) {
            srs_warn("stats disk not configed, disk iops disabled.");
        } else {
            string disks;
            for (int i = 0; i < (int)conf->args.size(); i++) {
                disks += conf->args.at(i);
                disks += " ";
            }
            srs_warn("stats disk list: %s", disks.c_str());
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check http api
    ////////////////////////////////////////////////////////////////////////
    if (get_http_api_listen().empty()) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive http_api listen invalid, listen=%s, ret=%d", get_http_api_listen().c_str(), ret);
        return ret;
    }

    ////////////////////////////////////////////////////////////////////////
    // check http stream
    ////////////////////////////////////////////////////////////////////////
    if (get_http_stream_listen().empty()) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive http_stream listen invalid, listen=%s, ret=%d", get_http_stream_listen().c_str(), ret);
        return ret;
    }
    ////////////////////////////////////////////////////////////////////////
    // check log name and level
    ////////////////////////////////////////////////////////////////////////
    if (true) {
        auto& log_filename = this->get_log_file();
        if (get_log_tank_file() && log_filename.empty()) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("must specifies the file to write log to. ret=%d", ret);
            return ret;
        }
        if (get_log_tank_file()) {
            srs_trace("write log to file %s", log_filename.c_str());
            //srs_trace("you can: tailf %s", log_filename.c_str());
        } else {
            srs_trace("write log to console");
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check features
    ////////////////////////////////////////////////////////////////////////
#ifndef SRS_AUTO_HTTP_SERVER
    if (get_http_stream_enabled()) {
        srs_warn("http_stream is disabled by configure");
    }
#endif
#ifndef SRS_AUTO_HTTP_API
    if (get_http_api_enabled()) {
        srs_warn("http_api is disabled by configure");
    }
#endif

    vector<SrsConfDirective*> stream_casters = get_stream_casters();
    for (int n = 0; n < (int)stream_casters.size(); n++) {
        auto stream_caster = stream_casters[n];
        if (stream_caster == nullptr) {
            continue;
        }
        for (int i = 0; i < (int)stream_caster->directives.size(); i++) {
            auto conf = stream_caster->at(i);
            if (conf == nullptr) {
                continue;
            }
            string& n = conf->name;
            if (n != "enabled" && n != "caster" && n != "output"
                    && n != "listen" && n != "rtp_port_min" && n != "rtp_port_max"
               ) {
                ret = ERROR_SYSTEM_CONFIG_INVALID;
                srs_error("unsupported stream_caster directive %s, ret=%d", n.c_str(), ret);
                return ret;
            }
        }
    }

    vector<SrsConfDirective*> vhosts;
    get_vhosts(vhosts);
    for (int n = 0; n < (int)vhosts.size(); n++) {
        SrsConfDirective* vhost = vhosts[n];
        for (int i = 0; vhost && i < (int)vhost->directives.size(); i++) {
            auto conf = vhost->at(i);
            if (conf != nullptr) {
                auto& n = conf->name;
                if (n != "enabled" && n != "chunk_size"
                        && n != "mode" && n != "origin" && n != "token_traverse" && n != "vhost"
                        && n != "dvr" && n != "ingest" && n != "hls" && n != "http_hooks"
                        && n != "gop_cache" && n != "queue_length"
                        && n != "refer" && n != "refer_publish" && n != "refer_play"
                        && n != "forward" && n != "transcode" && n != "bandcheck"
                        && n != "time_jitter" && n != "mix_correct"
                        && n != "atc" && n != "atc_auto"
                        && n != "debug_srs_upnode"
                        && n != "mr" && n != "mw_latency" && n != "min_latency" && n != "publish"
                        && n != "tcp_nodelay" && n != "send_min_interval" && n != "reduce_sequence_header"
                        && n != "publish_1stpkt_timeout" && n != "publish_normal_timeout"
                        && n != "security" && n != "http_remux"
                        && n != "http" && n != "http_static"
                        && n != "hds"
                   ) {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("unsupported vhost directive %s, ret=%d", n.c_str(), ret);
                    return ret;
                }
                // for each sub directives of vhost.
                if (n == "dvr") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "dvr_path" && m != "dvr_plan"
                                && m != "dvr_duration" && m != "dvr_wait_keyframe" && m != "time_jitter"
                           ) {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost dvr directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "mr") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "latency") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost mr directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "publish") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "parse_sps") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost publish directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "ingest") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "input" && m != "ffmpeg" && m != "engine") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost ingest directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "http" || n == "http_static") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "mount" && m != "dir") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost http directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "http_remux") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "mount" && m != "fast_cache" && m != "hstrs") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost http_remux directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "hls") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error"
                                && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec"
                                && m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify"
                                && m != "hls_wait_keyframe" && m != "hls_dispose"
                           ) {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost hls directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }

                        // TODO: FIXME: remove it in future.
                        if (m == "hls_storage" || m == "hls_mount") {
                            srs_warn("HLS RAM is removed from SRS2 to SRS3+");
                        }
                    }
                } else if (n == "http_hooks") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "on_connect" && m != "on_close" && m != "on_publish"
                                && m != "on_unpublish" && m != "on_play" && m != "on_stop"
                                && m != "on_dvr" && m != "on_hls" && m != "on_hls_notify"
                           ) {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost http_hooks directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "forward") {
                    // TODO: FIXME: implements it.
                    /*
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "vhost" && m != "refer") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost forward directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                    */
                } else if (n == "security") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "deny" && m != "allow") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost security directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                } else if (n == "transcode") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto trans = conf->at(j);
                        if (trans == nullptr) {
                            continue;
                        }
                        string& m = trans->name;
                        if (m != "enabled" && m != "ffmpeg" && m != "engine") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost transcode directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                        if (m == "engine") {
                            for (int k = 0; k < (int)trans->directives.size(); k++) {
                                auto sconf = trans->at(j);
                                if (sconf == nullptr) {
                                    continue;
                                }
                                string& e = sconf->name;
                                if (e != "enabled" && e != "vfilter" && e != "vcodec"
                                        && e != "vbitrate" && e != "vfps" && e != "vwidth" && e != "vheight"
                                        && e != "vthreads" && e != "vprofile" && e != "vpreset" && e != "vparams"
                                        && e != "acodec" && e != "abitrate" && e != "asample_rate" && e != "achannels"
                                        && e != "aparams" && e != "output"
                                        && e != "iformat" && e != "oformat"
                                   ) {
                                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                                    srs_error("unsupported vhost transcode engine directive %s, ret=%d", e.c_str(), ret);
                                    return ret;
                                }
                            }
                        }
                    }
                } else if (n == "bandcheck") {
                    for (int j = 0; j < (int)conf->directives.size(); j++) {
                        auto sconf = conf->at(j);
                        if (sconf == nullptr) {
                            continue;
                        }
                        string& m = sconf->name;
                        if (m != "enabled" && m != "key" && m != "interval" && m != "limit_kbps") {
                            ret = ERROR_SYSTEM_CONFIG_INVALID;
                            srs_error("unsupported vhost bandcheck directive %s, ret=%d", m.c_str(), ret);
                            return ret;
                        }
                    }
                }
            }
        }
    }
    // check ingest id unique.
    for (int i = 0; i < (int)vhosts.size(); i++) {
        auto vhost = vhosts[i];
        if (vhost == nullptr) {
            continue;
        }

        std::vector<string> ids;
        for (int j = 0; j < (int)vhost->directives.size(); j++) {
            auto conf = vhost->at(j);
            if (conf == nullptr || conf->name != "ingest") {
                continue;
            }

            auto& id = conf->arg0();
            for (int k = 0; k < (int)ids.size(); k++) {
                if (id == ids.at(k)) {
                    ret = ERROR_SYSTEM_CONFIG_INVALID;
                    srs_error("directive \"ingest\" id duplicated, vhost=%s, id=%s, ret=%d", vhost->name.c_str(), id.c_str(), ret);
                    return ret;
                }
            }
            ids.push_back(id);
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // check chunk size
    ////////////////////////////////////////////////////////////////////////
    if (get_global_chunk_size() < SRS_CONSTS_RTMP_MIN_CHUNK_SIZE
            || get_global_chunk_size() > SRS_CONSTS_RTMP_MAX_CHUNK_SIZE
       ) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("directive chunk_size invalid, chunk_size=%d, must in [%d, %d], ret=%d",
                  get_global_chunk_size(), SRS_CONSTS_RTMP_MIN_CHUNK_SIZE,
                  SRS_CONSTS_RTMP_MAX_CHUNK_SIZE, ret);
        return ret;
    }
    for (int i = 0; i < (int)vhosts.size(); i++) {
        SrsConfDirective* vhost = vhosts[i];
        if (vhost == nullptr) {
            continue;
        }
        if (get_chunk_size(vhost->arg0()) < SRS_CONSTS_RTMP_MIN_CHUNK_SIZE
                || get_chunk_size(vhost->arg0()) > SRS_CONSTS_RTMP_MAX_CHUNK_SIZE
           ) {
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("directive vhost %s chunk_size invalid, chunk_size=%d, must in [%d, %d], ret=%d",
                      vhost->arg0().c_str(), get_chunk_size(vhost->arg0()), SRS_CONSTS_RTMP_MIN_CHUNK_SIZE,
                      SRS_CONSTS_RTMP_MAX_CHUNK_SIZE, ret);
            return ret;
        }
    }
    for (int i = 0; i < (int)vhosts.size(); i++) {
        auto vhost = vhosts[i];
        if (vhost == nullptr) {
            continue;
        }
        //srs_assert(vhost != nullptr);
#ifndef SRS_AUTO_DVR
        if (get_dvr_enabled(vhost->arg0())) {
            srs_warn("dvr of vhost %s is disabled by configure", vhost->arg0().c_str());
        }
#endif
#ifndef SRS_AUTO_HLS
        if (get_hls_enabled(vhost->arg0())) {
            srs_warn("hls of vhost %s is disabled by configure", vhost->arg0().c_str());
        }
#endif
#ifndef SRS_AUTO_HTTP_CALLBACK
        if (get_vhost_http_hooks_enabled(vhost->arg0())) {
            srs_warn("http_hooks of vhost %s is disabled by configure", vhost->arg0().c_str());
        }
#endif
#ifndef SRS_AUTO_TRANSCODE
        if (get_transcode_enabled(get_transcode(vhost->arg0(), ""))) {
            srs_warn("transcode of vhost %s is disabled by configure", vhost->arg0().c_str());
        }
#endif
#ifndef SRS_AUTO_INGEST
        vector<SrsConfDirective*> ingesters = get_ingesters(vhost->arg0());
        for (int j = 0; j < (int)ingesters.size(); j++) {
            auto ingest = ingesters[j];
            if (get_ingest_enabled(ingest)) {
                srs_warn("ingest %s of vhost %s is disabled by configure",
                         ingest->arg0().c_str(), vhost->arg0().c_str()
                        );
            }
        }
#endif
        // TODO: FIXME: required http server when hls storage is ram or both.
    }

    // asprocess conflict with daemon
    if (get_asprocess() && get_deamon()) {
        ret = ERROR_SYSTEM_CONFIG_INVALID;
        srs_error("daemon conflict with asprocess, ret=%d", ret);
        return ret;
    }
    return ret;
}

void SrsConfig::append_dolphin() {
    // mock by dolphin mode.
    // for the dolphin will start srs with specified params.
    if (dolphin_rtmp_port.empty() == false || dolphin_http_port.empty() == false) {
        // for RTMP.
        set_config_directive(root, "listen", dolphin_rtmp_port);

        // for HTTP
        set_config_directive(root, "http_server", "");

        SrsConfDirective* http_server = root->get("http_server");
        set_config_directive(http_server, "enabled", "on");
        set_config_directive(http_server, "listen", dolphin_http_port);

        // others.
        set_config_directive(root, "daemon", "off");
        set_config_directive(root, "srs_log_tank", "console");
    }
}

const string& SrsConfig::cwd() {
    return _cwd;
}

const string& SrsConfig::argv() {
    return _argv;
}

bool SrsConfig::get_deamon() {
    srs_assert(root);
    auto conf = root->get("daemon");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

SrsConfDirective* SrsConfig::get_root() {
    return root;
}

int SrsConfig::get_max_connections() {
    srs_assert(root);
    auto conf = root->get("max_connections");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_MAX_CONNECTIONS;
    }
    return ::atoi(conf->arg0().c_str());
}

vector<string> SrsConfig::get_listens() {
    std::vector<string> ports;
    auto conf = root->get("listen");
    if (conf == nullptr) {
        return ports;
    }

    for (int i = 0; i < (int)conf->args.size(); i++) {
        ports.push_back(conf->args.at(i));
    }
    return ports;
}

const string& SrsConfig::get_pid_file() {
    auto conf = root->get("pid");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_PID_FILE;
    }
    return conf->arg0();
}

int SrsConfig::get_pithy_print_ms() {
    SrsConfDirective* pithy = root->get("pithy_print_ms");
    if (pithy == nullptr || pithy->arg0().empty()) {
        return SRS_CONF_DEFAULT_PITHY_PRINT_MS;
    }
    return ::atoi(pithy->arg0().c_str());
}

bool SrsConfig::get_utc_time() {
    auto conf = root->get("utc_time");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_UTC_TIME;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_work_dir() {
    auto conf = root->get("work_dir");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_LOCAL_DIR;
    }
    return conf->arg0();
}

bool SrsConfig::get_asprocess() {
    auto conf = root->get("asprocess");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

int SrsConfig::get_post_file_process() {
    auto conf = root->get("post_file_process");
    if (conf == nullptr || conf->arg0().empty()) {
        return 3;
    }
    return ::atoi(conf->arg0().c_str());
}

vector<SrsConfDirective*> SrsConfig::get_stream_casters() {
    std::vector<SrsConfDirective*> stream_casters;
    if (root != nullptr) {
        for (int i = 0; i < (int)root->directives.size(); i++) {
            auto conf = root->at(i);
            if (conf == nullptr || conf->is_stream_caster() == false) {
                continue;
            }
            stream_casters.push_back(conf);
        }
    }
    return stream_casters;
}

bool SrsConfig::get_stream_caster_enabled(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return false;
    }
    auto conf = sc->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_STREAM_CASTER_ENABLED;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_stream_caster_engine(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = sc->get("caster");
    if (conf == nullptr || conf->arg0().empty()) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

const string& SrsConfig::get_stream_caster_output(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = sc->get("output");
    if (conf == nullptr || conf->arg0().empty()) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

int SrsConfig::get_stream_caster_listen(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return 0;
    }
    auto conf = sc->get("listen");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_stream_caster_rtp_port_min(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return 0;
    }
    auto conf = sc->get("rtp_port_min");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_stream_caster_rtp_port_max(SrsConfDirective* sc) {
    if (sc == nullptr) {
        return 0;
    }
    auto conf = sc->get("rtp_port_max");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

SrsConfDirective* SrsConfig::get_vhost(const string& vhost) {
    if (root == nullptr) {
        return nullptr;
    }
    for (int i = 0; i < (int)root->directives.size(); i++) {
        auto conf = root->at(i);
        if (conf == nullptr || conf->is_vhost() == false) {
            continue;
        }
        if (conf->arg0() == vhost) {
            return conf;
        }
    }
    if (vhost != SRS_CONSTS_RTMP_DEFAULT_VHOST) {
        return get_vhost(SRS_CONSTS_RTMP_DEFAULT_VHOST);
    }
    return nullptr;
}

void SrsConfig::get_vhosts(vector<SrsConfDirective*>& vhosts) {
    if (root == nullptr) {
        return;
    }
    for (int i = 0; i < (int)root->directives.size(); i++) {
        auto conf = root->at(i);
        if (conf == nullptr || conf->is_vhost() == false) {
            continue;
        }
        vhosts.push_back(conf);
    }
}

bool SrsConfig::get_vhost_enabled(SrsConfDirective* vhost) {
    if (vhost == nullptr) {
        return false;
    }
    auto conf = vhost->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

bool SrsConfig::get_vhost_enabled(const string& host, SrsConfDirective* vhost) {
    vhost = vhost == nullptr ? get_vhost(host) : vhost;
    return get_vhost_enabled(vhost);
}

bool SrsConfig::get_gop_cache(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_GOP_CACHE;
    }
    conf = conf->get("gop_cache");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_GOP_CACHE;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

bool SrsConfig::get_debug_srs_upnode(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return true;
    }
    conf = conf->get("debug_srs_upnode");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

bool SrsConfig::get_atc(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("atc");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

bool SrsConfig::get_atc_auto(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_ATC_AUTO;
    }
    conf = conf->get("atc_auto");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_ATC_AUTO;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

int SrsConfig::get_time_jitter(const string& vhost, SrsConfDirective* conf) {
    string time_jitter = SRS_CONF_DEFAULT_TIME_JITTER;
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf != nullptr) {
        conf = conf->get("time_jitter");
        if (conf != nullptr && conf->arg0().empty() == false) {
            time_jitter = conf->arg0();
        }
    }
    return _srs_time_jitter_string2int(time_jitter);
}

bool SrsConfig::get_mix_correct(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_MIX_CORRECT;
    }
    conf = conf->get("mix_correct");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_MIX_CORRECT;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

double SrsConfig::get_queue_length(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_PLAY_QUEUE;
    }
    conf = conf->get("queue_length");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_PLAY_QUEUE;
    }
    return ::atoi(conf->arg0().c_str());
}

SrsConfDirective* SrsConfig::get_refer(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("refer");
}

SrsConfDirective* SrsConfig::get_refer_play(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("refer_play");
}

SrsConfDirective* SrsConfig::get_refer_publish(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("refer_publish");
}

int SrsConfig::get_chunk_size(const string& vhost, SrsConfDirective* conf) {
    if (vhost.empty()) {
        return get_global_chunk_size();
    }
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return get_global_chunk_size(); // vhost does not specify the chunk size, use the global instead.
    }
    conf = conf->get("chunk_size");
    if (conf == nullptr || conf->arg0().empty()) {
        return get_global_chunk_size(); // vhost does not specify the chunk size, use the global instead.
    }
    return ::atoi(conf->arg0().c_str());
}

bool SrsConfig::get_parse_sps(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return true;
    }
    conf = conf->get("publish");
    if (conf == nullptr) {
        return true;
    }
    conf = conf->get("parse_sps");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

bool SrsConfig::get_mr_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_MR_ENABLED;
    }
    conf = conf->get("mr");
    if (conf == nullptr) {
        return SRS_PERF_MR_ENABLED;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_MR_ENABLED;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

int SrsConfig::get_mr_sleep_ms(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_MR_SLEEP;
    }
    conf = conf->get("mr");
    if (conf == nullptr) {
        return SRS_PERF_MR_SLEEP;
    }
    conf = conf->get("latency");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_MR_SLEEP;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_mw_sleep_ms(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_MW_SLEEP;
    }
    conf = conf->get("mw_latency");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_MW_SLEEP;
    }
    return ::atoi(conf->arg0().c_str());
}

bool SrsConfig::get_realtime_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_PERF_MIN_LATENCY_ENABLED;
    }
    conf = conf->get("min_latency");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_PERF_MIN_LATENCY_ENABLED;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

bool SrsConfig::get_tcp_nodelay(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("tcp_nodelay");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

double SrsConfig::get_send_min_interval(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return 0.0;
    }
    conf = conf->get("send_min_interval");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0.0;
    }
    return ::atof(conf->arg0().c_str());
}

bool SrsConfig::get_reduce_sequence_header(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("reduce_sequence_header");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

int SrsConfig::get_publish_1stpkt_timeout(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return 20000;
    }
    conf = conf->get("publish_1stpkt_timeout");
    if (conf == nullptr || conf->arg0().empty()) {
        return 20000;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_publish_normal_timeout(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return 5000;
    }
    conf = conf->get("publish_normal_timeout");
    if (conf == nullptr || conf->arg0().empty()) {
        return 5000;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_global_chunk_size() {
    if (root == nullptr) {
        return 0;
    }
    auto conf = root->get("chunk_size");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONSTS_RTMP_SRS_CHUNK_SIZE;
    }
    return ::atoi(conf->arg0().c_str());
}

SrsConfDirective* SrsConfig::get_forward(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("forward");
}

SrsConfDirective* SrsConfig::get_vhost_http_hooks(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("http_hooks");
}

bool SrsConfig::get_vhost_http_hooks_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        conf = conf->get("enabled");
        if (conf != nullptr && conf->arg0().empty() == false) {
            return SRS_CONF_PERFER_FALSE(conf->arg0());
        }
    }
    if (etcd_callback_route.empty() == false) {
        return true;
    }
    return false;
}

static vector<string> g_def_vs;
vector<string> SrsConfig::get_vhost_on_connect(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_connect");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "connect"};
}

vector<string> SrsConfig::get_vhost_on_close(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_close");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "close"};
}

vector<string> SrsConfig::get_vhost_on_publish(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_publish");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "publish"};
}

vector<string> SrsConfig::get_vhost_on_unpublish(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_unpublish");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "unpublish"};
}

vector<string> SrsConfig::get_vhost_on_play(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_play");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "play"};
}

vector<string> SrsConfig::get_vhost_on_stop(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_stop");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "stop"};
}

vector<string> SrsConfig::get_vhost_on_dvr(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_dvr");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "dvrs"};
}

vector<string> SrsConfig::get_vhost_on_hls(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_hls");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string> {etcd_callback_route + "hls"};
}

vector<string> SrsConfig::get_vhost_on_hls_notify(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost_http_hooks(vhost) : conf;
    if (conf != nullptr) {
        auto sconf = conf->get("on_hls_notify");
        if (sconf != nullptr) {
            return sconf->args;
        }
    }
    if (etcd_callback_route.empty() == true) {
        return g_def_vs;
    }
    return vector<string>{etcd_callback_route + "hls_notify"};
}

bool SrsConfig::get_bw_check_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("bandcheck");
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_bw_check_key(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    conf = conf->get("bandcheck");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    conf = conf->get("key");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

int SrsConfig::get_bw_check_interval_ms(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL * 1000;
    }
    conf = conf->get("bandcheck");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL * 1000;
    }
    conf = conf->get("interval");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL * 1000;
    }
    return (int)(::atof(conf->arg0().c_str()) * 1000);
}

int SrsConfig::get_bw_check_limit_kbps(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
    }
    conf = conf->get("bandcheck");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
    }
    conf = conf->get("limit_kbps");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
    }
    return ::atoi(conf->arg0().c_str());
}

bool SrsConfig::get_vhost_is_edge(SrsConfDirective* conf) {
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("mode");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return "remote" == conf->arg0();
}

bool SrsConfig::get_vhost_is_edge(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    return get_vhost_is_edge(conf);
}

SrsConfDirective* SrsConfig::get_vhost_edge_origin(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("origin");
}

bool SrsConfig::get_vhost_edge_token_traverse(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE;
    }
    conf = conf->get("token_traverse");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_vhost_edge_transform_vhost(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST;
    }
    conf = conf->get("vhost");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST;
    }
    return conf->arg0();
}

bool SrsConfig::get_security_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_SECURITY_ENABLED;
    }
    auto security = conf->get("security");
    if (security == nullptr) {
        return SRS_CONF_DEFAULT_SECURITY_ENABLED;
    }
    conf = security->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_SECURITY_ENABLED;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

SrsConfDirective* SrsConfig::get_security_rules(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    auto security = conf->get("security");
    if (security == nullptr) {
        return nullptr;
    }
    return security;
}

SrsConfDirective* SrsConfig::get_transcode(const string& vhost, const string& scope, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    auto transcode = conf->get("transcode");
    if (transcode == nullptr) {
        return nullptr;
    }
    if (transcode->arg0() == scope) {
        return transcode;
    }
    return nullptr;
}

bool SrsConfig::get_transcode_enabled(SrsConfDirective* transcode) {
    if (transcode == nullptr) {
        return false;
    }
    auto conf = transcode->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_transcode_ffmpeg(SrsConfDirective* transcode) {
    if (transcode == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = transcode->get("ffmpeg");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

vector<SrsConfDirective*> SrsConfig::get_transcode_engines(SrsConfDirective* transcode) {
    vector<SrsConfDirective*> engines;
    if (transcode == nullptr) {
        return engines;
    }
    for (int i = 0; i < (int)transcode->directives.size(); i++) {
        auto conf = transcode->directives[i];
        if (conf != nullptr && conf->name == "engine") {
            engines.push_back(conf);
        }
    }
    return engines;
}

bool SrsConfig::get_engine_enabled(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return false;
    }
    auto conf = engine->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_engine_iformat(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return SRS_CONF_DEFAULT_TRANSCODE_IFORMAT;
    }
    auto conf = engine->get("iformat");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_TRANSCODE_IFORMAT;
    }
    return conf->arg0();
}

vector<string> SrsConfig::get_engine_vfilter(SrsConfDirective* engine) {
    vector<string> vfilter;
    if (engine == nullptr) {
        return vfilter;
    }
    auto conf = engine->get("vfilter");
    if (conf == nullptr) {
        return vfilter;
    }
    for (int i = 0; i < (int)conf->directives.size(); i++) {
        auto p = conf->directives[i];
        if (p == nullptr) {
            continue;
        }
        vfilter.push_back("-" + p->name);
        vfilter.push_back(p->arg0());
    }
    return vfilter;
}

const string& SrsConfig::get_engine_vcodec(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = engine->get("vcodec");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

int SrsConfig::get_engine_vbitrate(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("vbitrate");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

double SrsConfig::get_engine_vfps(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("vfps");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atof(conf->arg0().c_str());
}

int SrsConfig::get_engine_vwidth(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("vwidth");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_engine_vheight(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("vheight");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_engine_vthreads(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 1;
    }
    auto conf = engine->get("vthreads");
    if (conf == nullptr || conf->arg0().empty()) {
        return 1;
    }
    return ::atoi(conf->arg0().c_str());
}

const string& SrsConfig::get_engine_vprofile(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = engine->get("vprofile");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

const string& SrsConfig::get_engine_vpreset(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = engine->get("vpreset");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

vector<string> SrsConfig::get_engine_vparams(SrsConfDirective* engine) {
    vector<string> vparams;
    if (engine == nullptr) {
        return vparams;
    }
    auto conf = engine->get("vparams");
    if (conf == nullptr) {
        return vparams;
    }
    for (int i = 0; i < (int)conf->directives.size(); i++) {
        auto p = conf->directives[i];
        if (p == nullptr) {
            continue;
        }
        vparams.push_back("-" + p->name);
        vparams.push_back(p->arg0());
    }
    return vparams;
}

const string& SrsConfig::get_engine_acodec(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = engine->get("acodec");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

int SrsConfig::get_engine_abitrate(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("abitrate");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_engine_asample_rate(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("asample_rate");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

int SrsConfig::get_engine_achannels(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return 0;
    }
    auto conf = engine->get("achannels");
    if (conf == nullptr || conf->arg0().empty()) {
        return 0;
    }
    return ::atoi(conf->arg0().c_str());
}

vector<string> SrsConfig::get_engine_aparams(SrsConfDirective* engine) {
    vector<string> aparams;
    if (engine == nullptr) {
        return aparams;
    }
    auto conf = engine->get("aparams");
    if (conf == nullptr) {
        return aparams;
    }
    for (int i = 0; i < (int)conf->directives.size(); i++) {
        auto p = conf->directives[i];
        if (p == nullptr) {
            continue;
        }
        aparams.push_back("-" + p->name);
        aparams.push_back(p->arg0());
    }
    return aparams;
}

const string& SrsConfig::get_engine_oformat(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return SRS_CONF_DEFAULT_TRANSCODE_OFORMAT;
    }
    auto conf = engine->get("oformat");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_TRANSCODE_OFORMAT;
    }
    return conf->arg0();
}

const string& SrsConfig::get_engine_output(SrsConfDirective* engine) {
    if (engine == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = engine->get("output");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

vector<SrsConfDirective*> SrsConfig::get_ingesters(const string& vhost, SrsConfDirective* vhost_conf) {
    vector<SrsConfDirective*> ingeters;
    vhost_conf = vhost_conf == nullptr ? get_vhost(vhost) : vhost_conf;
    if (vhost_conf == nullptr) {
        return ingeters;
    }
    for (int i = 0; i < (int)vhost_conf->directives.size(); i++) {
        auto conf = vhost_conf->directives[i];
        if (conf->name == "ingest") {
            ingeters.push_back(conf);
        }
    }
    return ingeters;
}

SrsConfDirective* SrsConfig::get_ingest_by_id(const string& vhost, const string& ingest_id, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    conf = conf->get("ingest", ingest_id);
    return conf;
}

bool SrsConfig::get_ingest_enabled(SrsConfDirective* ingest) {
    if (ingest == nullptr) {
        return false;
    }
    auto conf = ingest->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_ingest_ffmpeg(SrsConfDirective* ingest) {
    if (ingest == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = ingest->get("ffmpeg");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

const string& SrsConfig::get_ingest_input_type(SrsConfDirective* ingest) {
    if (ingest == nullptr) {
        return SRS_CONF_DEFAULT_INGEST_TYPE_FILE;
    }
    auto conf = ingest->get("input");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_INGEST_TYPE_FILE;
    }
    conf = conf->get("type");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_INGEST_TYPE_FILE;
    }
    return conf->arg0();
}

const string& SrsConfig::get_ingest_input_url(SrsConfDirective* ingest) {
    if (ingest == nullptr) {
        return DEFAULT_STR;
    }
    auto conf = ingest->get("input");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    conf = conf->get("url");
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

bool SrsConfig::get_log_tank_file() {
    if (root == nullptr) {
        return false;
    }
    auto conf = root->get("srs_log_tank");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return conf->arg0() != SRS_CONF_DEFAULT_LOG_TANK_CONSOLE;
}

const string& SrsConfig::get_log_level() {
    if (root == nullptr) {
        return SRS_CONF_DEFAULT_LOG_LEVEL;
    }
    auto conf = root->get("srs_log_level");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_LOG_LEVEL;
    }
    return conf->arg0();
}

const string& SrsConfig::get_log_file() {
    if (root == nullptr) {
        return SRS_CONF_DEFAULT_LOG_FILE;
    }
    auto conf = root->get("srs_log_file");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_LOG_FILE;
    }
    return conf->arg0();
}

bool SrsConfig::get_ffmpeg_log_enabled() {
    string log = get_ffmpeg_log_dir();
    return log != SRS_CONSTS_NULL_FILE;
}

const string& SrsConfig::get_ffmpeg_log_dir() {
    if (root == nullptr) {
        return SRS_CONF_DEFAULT_FF_LOG_DIR;
    }
    auto conf = root->get("ff_log_dir");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_FF_LOG_DIR;
    }
    return conf->arg0();
}

SrsConfDirective* SrsConfig::get_hls(const string& vhost) {
    auto conf = get_vhost(vhost);
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("hls");
}

bool SrsConfig::get_hls_enabled(const string& vhost, SrsConfDirective* hls) {
    hls = hls == nullptr ? get_hls(vhost) : hls;
    if (hls == nullptr) {
        return false;
    }
    auto conf = hls->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_hls_entry_prefix(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_entry_prefix");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_entry_prefix");
        if (val.empty() == false) {
            return val;
        }
    }
    return DEFAULT_STR;
}

const string& SrsConfig::get_hls_path(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_path");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_path");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_PATH;
}

const string& SrsConfig::get_hls_m3u8_file(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_m3u8_file");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_m3u8_file");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_M3U8_FILE;
}

const string& SrsConfig::get_hls_ts_file(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_ts_file");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_ts_file");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_TS_FILE;
}

bool SrsConfig::get_hls_ts_floor(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_ts_floor");
        if (val.empty() == false) {
            return SRS_CONF_PERFER_FALSE(val);
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_ts_floor");
        if (val.empty() == false) {
            return SRS_CONF_PERFER_FALSE(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_TS_FLOOR;
}

double SrsConfig::get_hls_fragment(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->ensure_property_number("hls_fragment");
        if (val != nullptr) {
            return val->to_number();
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_fragment");
        if (val.empty() == false) {
            return ::stod(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_FRAGMENT;
}

double SrsConfig::get_hls_td_ratio(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->ensure_property_number("hls_td_ratio");
        if (val != nullptr) {
            return val->to_number();
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_td_ratio");
        if (val.empty() == false) {
            return ::stod(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_TD_RATIO;
}

double SrsConfig::get_hls_aof_ratio(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->ensure_property_number("hls_aof_ratio");
        if (val != nullptr) {
            return val->to_number();
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_aof_ratio");
        if (val.empty() == false) {
            return ::stod(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_AOF_RATIO;
}

double SrsConfig::get_hls_window(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->ensure_property_number("hls_window");
        if (val != nullptr) {
            return val->to_number();
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_window");
        if (val.empty() == false) {
            return ::stod(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_WINDOW;
}

const string& SrsConfig::get_hls_on_error(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_on_error");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_on_error");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_ON_ERROR;
}

const string& SrsConfig::get_hls_acodec(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_acodec");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_acodec");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_ACODEC;
}

const string& SrsConfig::get_hls_vcodec(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("hls_vcodec");
        if (val.empty() == false) {
            return val;
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_vcodec");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_HLS_VCODEC;
}

int SrsConfig::get_vhost_hls_nb_notify(const string& vhost, SrsConfDirective* hls) {
    hls = hls == nullptr ? get_hls(vhost) : hls;
    if (hls == nullptr) {
        return SRS_CONF_DEFAULT_HLS_NB_NOTIFY;
    }
    auto conf = hls->get("hls_nb_notify");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HLS_NB_NOTIFY;
    }
    return ::atoi(conf->arg0().c_str());
}

bool SrsConfig::get_hls_cleanup(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto clp = sobj->sure_property_string("hls_cleanup");
        if (clp.empty() == false) {
            return SRS_CONF_PERFER_TRUE(clp);
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_cleanup");
        if (val.empty() == false) {
            return SRS_CONF_PERFER_TRUE(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_CLEANUP;
}

int SrsConfig::get_hls_dispose(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->get_property("hls_dispose");
        if (val != nullptr && val->is_date() == true) {
            return (int)val->to_date();
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_dispose");
        if (val.empty() == false) {
            return std::stoi(val);
        }
    }
    return 0;
}

bool SrsConfig::get_hls_wait_keyframe(SrsConfDirective* hls, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto clp = sobj->sure_property_string("hls_wait_keyframe");
        if (clp.empty() == false) {
            return SRS_CONF_PERFER_TRUE(clp);
        }
    }
    if (hls != nullptr) {
        auto& val = hls->getArg("hls_wait_keyframe");
        if (val.empty() == false) {
            return SRS_CONF_PERFER_TRUE(val);
        }
    }
    return SRS_CONF_DEFAULT_HLS_WAIT_KEYFRAME;
}

SrsConfDirective* SrsConfig::get_hds(const string& vhost) {
    auto conf = get_vhost(vhost);
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("hds");
}

bool SrsConfig::get_hds_enabled(const string& vhost, SrsConfDirective* hds) {
    hds = hds == nullptr ? get_hds(vhost) : hds;
    if (hds == nullptr) {
        return false;
    }
    auto conf = hds->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_hds_path(const string& vhost, SrsConfDirective* hds) {
    hds = hds == nullptr ? get_hds(vhost) : hds;
    if (hds == nullptr) {
        return SRS_CONF_DEFAULT_HDS_PATH;
    }
    auto conf = hds->get("hds_path");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HDS_PATH;
    }
    return conf->arg0();
}

double SrsConfig::get_hds_fragment(const string& vhost, SrsConfDirective* hds) {
    hds = hds == nullptr ? get_hds(vhost) : hds;
    if (hds == nullptr) {
        return SRS_CONF_DEFAULT_HDS_FRAGMENT;
    }
    auto conf = hds->get("hds_fragment");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HDS_FRAGMENT;
    }
    return ::atof(conf->arg0().c_str());
}

double SrsConfig::get_hds_window(const string& vhost, SrsConfDirective* hds) {
    hds = hds == nullptr ? get_hds(vhost) : hds;
    if (hds == nullptr) {
        return SRS_CONF_DEFAULT_HDS_WINDOW;
    }
    auto conf = hds->get("hds_window");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HDS_WINDOW;
    }
    return ::atof(conf->arg0().c_str());
}

SrsConfDirective* SrsConfig::get_dvr(const string& vhost) {
    auto conf = get_vhost(vhost);
    if (conf == nullptr) {
        return nullptr;
    }
    return conf->get("dvr");
}

bool SrsConfig::get_dvr_enabled(const string& vhost, SrsConfDirective* dvr) {
    dvr = dvr == nullptr ? get_dvr(vhost) : dvr;
    if (dvr == nullptr) {
        return false;
    }
    auto conf = dvr->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_dvr_path(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("dvr_path");
        if (val.empty() == false) {
            return val;
        }
    }
    if (dvr != nullptr) {
        auto& val = dvr->getArg("dvr_path");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_DVR_PATH;
}

const string& SrsConfig::get_dvr_plan(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("dvr_plan");
        if (val.empty() == false) {
            return val;
        }
    }
    if (dvr != nullptr) {
        auto& val = dvr->getArg("dvr_plan");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_DVR_PLAN;
}

const std::string& SrsConfig::get_dvr_store(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto& val = sobj->sure_property_string("dvr_store");
        if (val.empty() == false) {
            return val;
        }
    }
    if (dvr != nullptr) {
        auto& val = dvr->getArg("dvr_store");
        if (val.empty() == false) {
            return val;
        }
    }
    return SRS_CONF_DEFAULT_EMPTY_STR;
}

int SrsConfig::get_dvr_duration(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto val = sobj->get_property("dvr_duration");
        if (val != nullptr && val->is_date() == true) {
            return (int)val->to_date();
        }
    }
    if (dvr != nullptr) {
        auto& val = dvr->getArg("dvr_duration");
        if (val.empty() == false) {
            return ::stoi(val);
        }
    }
    return SRS_CONF_DEFAULT_DVR_DURATION;
}

bool SrsConfig::get_dvr_wait_keyframe(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    if (sobj != nullptr) {
        auto clp = sobj->sure_property_string("dvr_wait_keyframe");
        if (clp.empty() == false) {
            return SRS_CONF_PERFER_TRUE(clp);
        }
    }
    if (dvr != nullptr) {
        return SRS_CONF_PERFER_TRUE(dvr->getArg("dvr_wait_keyframe"));
    }
    return true;
}

int SrsConfig::get_dvr_time_jitter(SrsConfDirective* dvr, SrsAmf0Object* sobj) {
    string time_jitter;
    if (sobj != nullptr) {
        time_jitter = sobj->sure_property_string("time_jitter");
    }
    if (dvr != nullptr && time_jitter.empty() == true) {
        time_jitter = dvr->getArg("time_jitter");
    }
    if (time_jitter.empty() == true) {
        time_jitter = SRS_CONF_DEFAULT_TIME_JITTER;
    }
    return _srs_time_jitter_string2int(time_jitter);
}

SrsConfDirective* SrsConfig::get_http_api() {
    return root->get("http_api");
}

bool SrsConfig::get_http_api_enabled(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_api() : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_http_api_listen(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_api() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_API_PORT;
    }
    conf = conf->get("listen");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_API_PORT;
    }
    return conf->arg0();
}

bool SrsConfig::get_http_api_crossdomain(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_api() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN;
    }
    conf = conf->get("crossdomain");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

SrsConfDirective* SrsConfig::get_http_stream() {
    auto conf = root->get("http_stream");
    // http_stream renamed to http_server in SRS2.
    if (conf == nullptr) {
        conf = root->get("http_server");
    }
    return conf;
}

bool SrsConfig::get_http_stream_enabled(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_stream() : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_http_stream_listen(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_stream() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_STREAM_PORT;
    }
    conf = conf->get("listen");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_STREAM_PORT;
    }
    return conf->arg0();
}

const string& SrsConfig::get_http_stream_dir(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_http_stream() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_DIR;
    }
    conf = conf->get("dir");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_DIR;
    }
    if (conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_DIR;
    }
    return conf->arg0();
}

bool SrsConfig::get_vhost_http_enabled(const string& vhost, SrsConfDirective* vconf) {
    vconf = vconf == nullptr ? get_vhost(vhost) : vconf;
    if (vconf == nullptr) {
        return false;
    }
    auto conf = vconf->get("http");
    if (conf == nullptr) {
        conf = vconf->get("http_static");
    }
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

const string& SrsConfig::get_vhost_http_mount(const string& vhost, SrsConfDirective* vconf) {
    vconf = vconf == nullptr ? get_vhost(vhost) : vconf;
    if (vconf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_MOUNT;
    }
    auto conf = vconf->get("http");
    if (conf == nullptr) {
        conf = vconf->get("http_static");
        if (conf == nullptr) {
            return SRS_CONF_DEFAULT_HTTP_MOUNT;
        }
    }
    conf = conf->get("mount");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_MOUNT;
    }
    return conf->arg0();
}

const string& SrsConfig::get_vhost_http_dir(const string& vhost, SrsConfDirective* vconf) {
    vconf = vconf == nullptr ? get_vhost(vhost) : vconf;
    if (vconf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_DIR;
    }
    auto conf = vconf->get("http");
    if (conf == nullptr) {
        conf = vconf->get("http_static");
        if (conf == nullptr) {
            return SRS_CONF_DEFAULT_HTTP_DIR;
        }
    }
    conf = conf->get("dir");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_DIR;
    }
    return conf->arg0();
}

bool SrsConfig::get_vhost_http_remux_enabled(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("http_remux");
    if (conf == nullptr) {
        return false;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return false;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

double SrsConfig::get_vhost_http_remux_fast_cache(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE;
    }
    conf = conf->get("http_remux");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE;
    }
    conf = conf->get("fast_cache");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE;
    }
    if (conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE;
    }
    return ::atof(conf->arg0().c_str());
}

const string& SrsConfig::get_vhost_http_remux_mount(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT;
    }
    conf = conf->get("http_remux");
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT;
    }
    conf = conf->get("mount");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT;
    }
    return conf->arg0();
}

bool SrsConfig::get_vhost_http_remux_hstrs(const string& vhost, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_vhost(vhost) : conf;
    if (conf == nullptr) {
        return true;
    }
    conf = conf->get("http_remux");
    if (conf == nullptr) {
        return true;
    }
    conf = conf->get("hstrs");
    if (conf == nullptr || conf->arg0().empty()) {
        return true;
    }
    return SRS_CONF_PERFER_TRUE(conf->arg0());
}

SrsConfDirective* SrsConfig::get_heartbeart() {
    return root->get("heartbeat");
}

bool SrsConfig::get_heartbeat_enabled(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_heartbeart() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED;
    }
    conf = conf->get("enabled");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

int64_t SrsConfig::get_heartbeat_interval(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_heartbeart() : conf;
    if (conf == nullptr) {
        return (int64_t)(SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL * 1000);
    }
    conf = conf->get("interval");
    if (conf == nullptr || conf->arg0().empty()) {
        return (int64_t)(SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL * 1000);
    }
    return (int64_t)(::atof(conf->arg0().c_str()) * 1000);
}

const string& SrsConfig::get_heartbeat_url(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_heartbeart() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_URL;
    }
    conf = conf->get("url");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_URL;
    }
    return conf->arg0();
}

const string& SrsConfig::get_heartbeat_device_id(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_heartbeart() : conf;
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    conf = conf->get("device_id");
    if (conf == nullptr || conf->arg0().empty()) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

bool SrsConfig::get_heartbeat_summaries(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_heartbeart() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_SUMMARIES;
    }
    conf = conf->get("summaries");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_SUMMARIES;
    }
    return SRS_CONF_PERFER_FALSE(conf->arg0());
}

SrsConfDirective* SrsConfig::get_stats() {
    return root->get("stats");
}

int SrsConfig::get_stats_network(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_stats() : conf;
    if (conf == nullptr) {
        return SRS_CONF_DEFAULT_STATS_NETWORK_DEVICE_INDEX;
    }
    conf = conf->get("network");
    if (conf == nullptr || conf->arg0().empty()) {
        return SRS_CONF_DEFAULT_STATS_NETWORK_DEVICE_INDEX;
    }
    return ::atoi(conf->arg0().c_str());
}

SrsConfDirective* SrsConfig::get_stats_disk_device(SrsConfDirective* conf) {
    conf = conf == nullptr ? get_stats() : conf;
    if (conf == nullptr) {
        return nullptr;
    }
    conf = conf->get("disk");
    if (conf == nullptr || conf->args.size() == 0) {
        return nullptr;
    }
    return conf;
}

SrsConfDirective* SrsConfig::get_etcd() {
    return root->get("etcd");
}

const string& SrsConfig::get_etcd_key(const string& key, SrsConfDirective* conf) {
    conf = conf == nullptr ? get_etcd() : conf;
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    conf = conf->get(key);
    if (conf == nullptr) {
        return DEFAULT_STR;
    }
    return conf->arg0();
}

const string& SrsConfig::get_etcd_report_addr(SrsConfDirective* conf) {
    return get_etcd_key("report", conf);
}

const string& SrsConfig::get_etcd_srv_key(SrsConfDirective* conf) {
    return get_etcd_key("key", conf);
}

const string& SrsConfig::get_etcd_srv_mgr(SrsConfDirective* conf) {
    return get_etcd_key("mgr", conf);
}

const string& SrsConfig::get_etcd_srv_type(SrsConfDirective* conf) {
    return get_etcd_key("type", conf);
}

const string SrsConfig::get_etcd_srv_addr(SrsConfDirective* conf) {
    auto addr = getEnv("HS_POD_IP");
    if (addr.empty() == false) {
        return addr;
    }

    addr = getEnv("HS_DOMAIN");
    if (addr.empty() == false) {
        return addr;
    }

    addr = get_etcd_key("addr", conf);
    if (addr.empty() == false) {
        return addr;
    }

    auto& ips = srs_get_local_ipv4_ips();
    int index = get_stats_network();
    if (index >= (int)ips.size()) {
        return "";
    }
    return ips[index];
}

int SrsConfig::get_etcd_srv_ttl(SrsConfDirective* conf) {
    auto sttl = get_etcd_key("ttl", conf);
    if (sttl.empty() == true) {
        return 2;
    }
    return std::stoi(sttl);
}

bool SrsConfig::get_etcd_report_log(SrsConfDirective* conf) {
    auto rptlog = get_etcd_key("report_log", conf);
    if (rptlog.empty() == false && rptlog == "on") {
        return true;
    }
    return false;
}

namespace _srs_internal {

SrsConfigBuffer::SrsConfigBuffer() {
    line = 1;
    pos = last = start = nullptr;
    end = start;
}

SrsConfigBuffer::~SrsConfigBuffer() {
    srs_freepa(start);
}

int SrsConfigBuffer::fullfill(const char* filename) {
    int ret = ERROR_SUCCESS;

    SrsFileReader reader;

    // open file reader.
    if ((ret = reader.open(filename)) != ERROR_SUCCESS) {
        srs_error("open conf file error. ret=%d", ret);
        return ret;
    }

    // read all.
    int filesize = (int)reader.filesize();

    // create buffer
    srs_freepa(start);
    pos = last = start = new char[filesize];
    end = start + filesize;

    // read total content from file.
    ssize_t nread = 0;
    if ((ret = reader.read(start, filesize, &nread)) != ERROR_SUCCESS) {
        srs_error("read file read error. expect %d, actual %d bytes, ret=%d",
                  filesize, nread, ret);
        return ret;
    }
    return ret;
}

bool SrsConfigBuffer::empty() {
    return pos >= end;
}

};

bool srs_directive_equals(SrsConfDirective* a, SrsConfDirective* b) {
    // both nullptr, equal.
    if (!a && !b) {
        return true;
    }

    if (!a || !b) {
        return false;
    }

    if (a->name != b->name) {
        return false;
    }

    if (a->args.size() != b->args.size()) {
        return false;
    }

    for (int i = 0; i < (int)a->args.size(); i++) {
        if (a->args.at(i) != b->args.at(i)) {
            return false;
        }
    }

    if (a->directives.size() != b->directives.size()) {
        return false;
    }

    for (int i = 0; i < (int)a->directives.size(); i++) {
        SrsConfDirective* a0 = a->at(i);
        SrsConfDirective* b0 = b->at(i);
        if (!srs_directive_equals(a0, b0)) {
            return false;
        }
    }
    return true;
}

bool srs_config_hls_is_on_error_ignore(const string& strategy) {
    return strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE;
}

bool srs_config_hls_is_on_error_continue(const string& strategy) {
    return strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE;
}

bool srs_config_ingest_is_file(const string& type) {
    return type == SRS_CONF_DEFAULT_INGEST_TYPE_FILE;
}

bool srs_config_ingest_is_stream(const string& type) {
    return type == SRS_CONF_DEFAULT_INGEST_TYPE_STREAM;
}

bool srs_config_dvr_is_plan_segment(const string& plan) {
    return plan == SRS_CONF_DEFAULT_DVR_PLAN_SEGMENT;
}

bool srs_config_dvr_is_plan_session(const string& plan) {
    return plan == SRS_CONF_DEFAULT_DVR_PLAN_SESSION;
}

bool srs_config_dvr_is_plan_append(const string& plan) {
    return plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND;
}

bool srs_stream_caster_is_udp(const string& caster) {
    return caster == SRS_CONF_DEFAULT_STREAM_CASTER_MPEGTS_OVER_UDP;
}

bool srs_stream_caster_is_rtsp(const string& caster) {
    return caster == SRS_CONF_DEFAULT_STREAM_CASTER_RTSP;
}

bool srs_stream_caster_is_flv(const string& caster) {
    return caster == SRS_CONF_DEFAULT_STREAM_CASTER_FLV;
}
